@swarmclawai/swarmclaw 0.7.6 → 0.7.8
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 +19 -10
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +16 -0
- package/src/app/api/agents/route.ts +2 -0
- package/src/app/api/chats/[id]/route.ts +21 -1
- package/src/app/api/chats/route.ts +13 -1
- package/src/app/api/connectors/[id]/route.ts +20 -2
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
- package/src/app/api/external-agents/[id]/route.ts +38 -6
- package/src/app/api/external-agents/route.ts +17 -1
- package/src/app/api/gateways/[id]/health/route.ts +8 -0
- package/src/app/api/gateways/[id]/route.ts +53 -1
- package/src/app/api/gateways/route.ts +53 -0
- package/src/app/api/openclaw/deploy/route.ts +139 -0
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- 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/cli/index.js +40 -0
- package/src/cli/index.test.js +68 -0
- package/src/cli/spec.js +60 -0
- package/src/components/agents/agent-sheet.tsx +281 -33
- package/src/components/auth/setup-wizard.tsx +75 -2
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-header.tsx +4 -0
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/gateways/gateway-sheet.tsx +140 -8
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/openclaw/openclaw-deploy-panel.tsx +591 -9
- 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 +221 -17
- package/src/components/shared/settings/section-capability-policy.tsx +38 -0
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/tasks/approvals-panel.tsx +177 -18
- package/src/components/tasks/task-board.tsx +137 -23
- package/src/components/tasks/task-card.tsx +29 -0
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/lib/server/agent-runtime-config.ts +142 -7
- package/src/lib/server/agent-thread-session.ts +9 -1
- package/src/lib/server/capability-router.test.ts +22 -0
- package/src/lib/server/capability-router.ts +54 -18
- package/src/lib/server/chat-execution.ts +33 -3
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.ts +99 -74
- package/src/lib/server/daemon-state.ts +83 -46
- package/src/lib/server/elevenlabs.test.ts +59 -1
- package/src/lib/server/heartbeat-service.ts +5 -1
- 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/openclaw-deploy.test.ts +8 -0
- package/src/lib/server/openclaw-deploy.ts +679 -19
- package/src/lib/server/orchestrator-lg.ts +1 -0
- package/src/lib/server/orchestrator.ts +11 -0
- package/src/lib/server/plugins.ts +6 -1
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-followups.test.ts +147 -2
- package/src/lib/server/queue.ts +278 -8
- package/src/lib/server/session-run-manager.ts +31 -0
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.ts +26 -1
- package/src/lib/server/session-tools/context.ts +5 -0
- package/src/lib/server/session-tools/crud.ts +265 -76
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +38 -2
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.ts +14 -2
- 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/web-inputs.test.ts +17 -0
- package/src/lib/server/session-tools/web.ts +153 -6
- package/src/lib/server/stream-agent-chat.test.ts +27 -2
- package/src/lib/server/stream-agent-chat.ts +104 -30
- package/src/lib/server/tool-aliases.ts +2 -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-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +269 -0
- package/src/lib/setup-defaults.ts +2 -2
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/validation/schemas.ts +9 -0
- package/src/types/index.ts +104 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import {
|
|
3
3
|
buildOpenClawDeployBundle,
|
|
4
|
+
deployOpenClawOverSsh,
|
|
4
5
|
getOpenClawLocalDeployStatus,
|
|
6
|
+
getOpenClawRemoteDeployStatus,
|
|
7
|
+
restartOpenClawLocalDeploy,
|
|
8
|
+
runOpenClawRemoteLifecycle,
|
|
5
9
|
startOpenClawLocalDeploy,
|
|
6
10
|
stopOpenClawLocalDeploy,
|
|
11
|
+
verifyOpenClawDeployment,
|
|
12
|
+
type OpenClawExposurePreset,
|
|
7
13
|
type OpenClawRemoteDeployProvider,
|
|
8
14
|
type OpenClawRemoteDeployTemplate,
|
|
15
|
+
type OpenClawSshConfig,
|
|
16
|
+
type OpenClawUseCaseTemplate,
|
|
9
17
|
} from '@/lib/server/openclaw-deploy'
|
|
10
18
|
|
|
11
19
|
export const dynamic = 'force-dynamic'
|
|
@@ -19,6 +27,12 @@ function parsePort(value: unknown): number | undefined {
|
|
|
19
27
|
return Number.isFinite(parsed) ? parsed : undefined
|
|
20
28
|
}
|
|
21
29
|
|
|
30
|
+
function parseIntBounded(value: unknown, fallback: number, min: number, max: number): number {
|
|
31
|
+
const parsed = parsePort(value)
|
|
32
|
+
if (typeof parsed !== 'number') return fallback
|
|
33
|
+
return Math.max(min, Math.min(max, parsed))
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
function parseTemplate(value: unknown): OpenClawRemoteDeployTemplate | undefined {
|
|
23
37
|
if (value === 'docker' || value === 'render' || value === 'fly' || value === 'railway') {
|
|
24
38
|
return value
|
|
@@ -43,9 +57,48 @@ function parseProvider(value: unknown): OpenClawRemoteDeployProvider | undefined
|
|
|
43
57
|
return undefined
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
function parseUseCase(value: unknown): OpenClawUseCaseTemplate | undefined {
|
|
61
|
+
if (
|
|
62
|
+
value === 'local-dev'
|
|
63
|
+
|| value === 'single-vps'
|
|
64
|
+
|| value === 'private-tailnet'
|
|
65
|
+
|| value === 'browser-heavy'
|
|
66
|
+
|| value === 'team-control'
|
|
67
|
+
) {
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseExposure(value: unknown): OpenClawExposurePreset | undefined {
|
|
74
|
+
if (
|
|
75
|
+
value === 'private-lan'
|
|
76
|
+
|| value === 'tailscale'
|
|
77
|
+
|| value === 'caddy'
|
|
78
|
+
|| value === 'nginx'
|
|
79
|
+
|| value === 'ssh-tunnel'
|
|
80
|
+
) {
|
|
81
|
+
return value
|
|
82
|
+
}
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseSsh(value: unknown): Partial<OpenClawSshConfig> | null {
|
|
87
|
+
if (!value || typeof value !== 'object') return null
|
|
88
|
+
const ssh = value as Record<string, unknown>
|
|
89
|
+
return {
|
|
90
|
+
host: typeof ssh.host === 'string' ? ssh.host : '',
|
|
91
|
+
user: typeof ssh.user === 'string' ? ssh.user : null,
|
|
92
|
+
port: parsePort(ssh.port),
|
|
93
|
+
keyPath: typeof ssh.keyPath === 'string' ? ssh.keyPath : null,
|
|
94
|
+
targetDir: typeof ssh.targetDir === 'string' ? ssh.targetDir : null,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
46
98
|
export async function GET() {
|
|
47
99
|
return NextResponse.json({
|
|
48
100
|
local: getOpenClawLocalDeployStatus(),
|
|
101
|
+
remote: getOpenClawRemoteDeployStatus(),
|
|
49
102
|
})
|
|
50
103
|
}
|
|
51
104
|
|
|
@@ -73,6 +126,18 @@ export async function POST(req: Request) {
|
|
|
73
126
|
})
|
|
74
127
|
}
|
|
75
128
|
|
|
129
|
+
if (action === 'restart-local') {
|
|
130
|
+
const result = await restartOpenClawLocalDeploy({
|
|
131
|
+
port: parsePort(body.port),
|
|
132
|
+
token: typeof body.token === 'string' ? body.token : null,
|
|
133
|
+
})
|
|
134
|
+
return NextResponse.json({
|
|
135
|
+
ok: true,
|
|
136
|
+
local: result.local,
|
|
137
|
+
token: result.token,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
76
141
|
if (action === 'bundle') {
|
|
77
142
|
const bundle = buildOpenClawDeployBundle({
|
|
78
143
|
template: parseTemplate(body.template),
|
|
@@ -81,6 +146,8 @@ export async function POST(req: Request) {
|
|
|
81
146
|
scheme: body.scheme === 'http' ? 'http' : 'https',
|
|
82
147
|
port: parsePort(body.port),
|
|
83
148
|
provider: parseProvider(body.provider),
|
|
149
|
+
useCase: parseUseCase(body.useCase),
|
|
150
|
+
exposure: parseExposure(body.exposure),
|
|
84
151
|
})
|
|
85
152
|
return NextResponse.json({
|
|
86
153
|
ok: true,
|
|
@@ -88,6 +155,78 @@ export async function POST(req: Request) {
|
|
|
88
155
|
})
|
|
89
156
|
}
|
|
90
157
|
|
|
158
|
+
if (action === 'ssh-deploy') {
|
|
159
|
+
const result = await deployOpenClawOverSsh({
|
|
160
|
+
template: parseTemplate(body.template),
|
|
161
|
+
target: typeof body.target === 'string' ? body.target : null,
|
|
162
|
+
token: typeof body.token === 'string' ? body.token : null,
|
|
163
|
+
scheme: body.scheme === 'http' ? 'http' : 'https',
|
|
164
|
+
port: parsePort(body.port),
|
|
165
|
+
provider: parseProvider(body.provider),
|
|
166
|
+
useCase: parseUseCase(body.useCase),
|
|
167
|
+
exposure: parseExposure(body.exposure),
|
|
168
|
+
ssh: parseSsh(body.ssh),
|
|
169
|
+
})
|
|
170
|
+
return NextResponse.json({
|
|
171
|
+
ok: result.ok,
|
|
172
|
+
remote: getOpenClawRemoteDeployStatus(),
|
|
173
|
+
processId: result.processId || null,
|
|
174
|
+
token: result.token,
|
|
175
|
+
bundle: result.bundle,
|
|
176
|
+
summary: result.summary,
|
|
177
|
+
commandPreview: result.commandPreview,
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
action === 'remote-start'
|
|
183
|
+
|| action === 'remote-stop'
|
|
184
|
+
|| action === 'remote-restart'
|
|
185
|
+
|| action === 'remote-upgrade'
|
|
186
|
+
|| action === 'remote-backup'
|
|
187
|
+
|| action === 'remote-restore'
|
|
188
|
+
|| action === 'remote-rotate-token'
|
|
189
|
+
) {
|
|
190
|
+
const actionMap = {
|
|
191
|
+
'remote-start': 'start',
|
|
192
|
+
'remote-stop': 'stop',
|
|
193
|
+
'remote-restart': 'restart',
|
|
194
|
+
'remote-upgrade': 'upgrade',
|
|
195
|
+
'remote-backup': 'backup',
|
|
196
|
+
'remote-restore': 'restore',
|
|
197
|
+
'remote-rotate-token': 'rotate-token',
|
|
198
|
+
} as const
|
|
199
|
+
const lifecycleAction = action as keyof typeof actionMap
|
|
200
|
+
const result = await runOpenClawRemoteLifecycle({
|
|
201
|
+
action: actionMap[lifecycleAction],
|
|
202
|
+
ssh: parseSsh(body.ssh),
|
|
203
|
+
token: typeof body.token === 'string' ? body.token : null,
|
|
204
|
+
backupPath: typeof body.backupPath === 'string' ? body.backupPath : null,
|
|
205
|
+
})
|
|
206
|
+
return NextResponse.json({
|
|
207
|
+
ok: result.ok,
|
|
208
|
+
remote: getOpenClawRemoteDeployStatus(),
|
|
209
|
+
processId: result.processId || null,
|
|
210
|
+
token: result.token,
|
|
211
|
+
summary: result.summary,
|
|
212
|
+
commandPreview: result.commandPreview,
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (action === 'verify') {
|
|
217
|
+
const result = await verifyOpenClawDeployment({
|
|
218
|
+
endpoint: typeof body.endpoint === 'string' ? body.endpoint : null,
|
|
219
|
+
credentialId: typeof body.credentialId === 'string' ? body.credentialId : null,
|
|
220
|
+
token: typeof body.token === 'string' ? body.token : null,
|
|
221
|
+
model: typeof body.model === 'string' ? body.model : null,
|
|
222
|
+
timeoutMs: parseIntBounded(body.timeoutMs, 8000, 1000, 30000),
|
|
223
|
+
})
|
|
224
|
+
return NextResponse.json({
|
|
225
|
+
ok: result.ok,
|
|
226
|
+
verify: result,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
91
230
|
return NextResponse.json({ ok: false, error: 'Unknown deploy action.' }, { status: 400 })
|
|
92
231
|
} catch (err: unknown) {
|
|
93
232
|
return NextResponse.json(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadProjects, saveProjects, deleteProject, loadAgents, saveAgents, loadTasks, saveTasks, loadSchedules, saveSchedules, loadSkills, saveSkills } from '@/lib/server/storage'
|
|
2
|
+
import { loadProjects, saveProjects, deleteProject, loadAgents, saveAgents, loadTasks, saveTasks, loadSchedules, saveSchedules, loadSkills, saveSkills, loadSecrets, saveSecrets } from '@/lib/server/storage'
|
|
3
3
|
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { ensureProjectWorkspace, normalizeProjectPatchInput } from '@/lib/server/project-utils'
|
|
4
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
6
|
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -17,12 +18,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
17
18
|
const { id } = await params
|
|
18
19
|
const body = await req.json()
|
|
19
20
|
const result = mutateItem(ops, id, (project) => {
|
|
20
|
-
|
|
21
|
+
const patch = normalizeProjectPatchInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
|
|
22
|
+
Object.assign(project, patch, { updatedAt: Date.now() })
|
|
21
23
|
delete (project as Record<string, unknown>).id
|
|
22
24
|
project.id = id
|
|
23
25
|
return project
|
|
24
26
|
})
|
|
25
27
|
if (!result) return notFound()
|
|
28
|
+
ensureProjectWorkspace(id, result.name)
|
|
26
29
|
return NextResponse.json(result)
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -50,6 +53,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
50
53
|
clearProjectId(loadTasks, saveTasks, 'tasks')
|
|
51
54
|
clearProjectId(loadSchedules, saveSchedules, 'schedules')
|
|
52
55
|
clearProjectId(loadSkills, saveSkills, 'skills')
|
|
56
|
+
clearProjectId(loadSecrets, saveSecrets, 'secrets')
|
|
53
57
|
|
|
54
58
|
return NextResponse.json({ ok: true })
|
|
55
59
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
3
|
import { loadProjects, saveProjects } from '@/lib/server/storage'
|
|
4
|
+
import { ensureProjectWorkspace, normalizeProjectCreateInput } from '@/lib/server/project-utils'
|
|
4
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
6
|
export const dynamic = 'force-dynamic'
|
|
6
7
|
|
|
@@ -13,15 +14,15 @@ export async function POST(req: Request) {
|
|
|
13
14
|
const id = genId()
|
|
14
15
|
const now = Date.now()
|
|
15
16
|
const projects = loadProjects()
|
|
17
|
+
const normalized = normalizeProjectCreateInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
|
|
16
18
|
projects[id] = {
|
|
17
19
|
id,
|
|
18
|
-
|
|
19
|
-
description: body.description || '',
|
|
20
|
-
color: body.color || undefined,
|
|
20
|
+
...normalized,
|
|
21
21
|
createdAt: now,
|
|
22
22
|
updatedAt: now,
|
|
23
23
|
}
|
|
24
24
|
saveProjects(projects)
|
|
25
|
+
ensureProjectWorkspace(id, projects[id].name)
|
|
25
26
|
notify('projects')
|
|
26
27
|
return NextResponse.json(projects[id])
|
|
27
28
|
}
|
|
@@ -19,6 +19,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
19
19
|
if (body.service !== undefined) secret.service = body.service
|
|
20
20
|
if (body.scope !== undefined) secret.scope = body.scope
|
|
21
21
|
if (body.agentIds !== undefined) secret.agentIds = body.agentIds
|
|
22
|
+
if (body.projectId !== undefined) secret.projectId = body.projectId || undefined
|
|
22
23
|
secret.updatedAt = Date.now()
|
|
23
24
|
return secret
|
|
24
25
|
})
|
|
@@ -10,7 +10,7 @@ export async function GET(_req: Request) {
|
|
|
10
10
|
const safe = Object.fromEntries(
|
|
11
11
|
Object.entries(secrets).map(([id, s]: [string, any]) => [
|
|
12
12
|
id,
|
|
13
|
-
{ id: s.id, name: s.name, service: s.service, scope: s.scope, agentIds: s.agentIds, createdAt: s.createdAt, updatedAt: s.updatedAt },
|
|
13
|
+
{ id: s.id, name: s.name, service: s.service, scope: s.scope, agentIds: s.agentIds, projectId: s.projectId, createdAt: s.createdAt, updatedAt: s.updatedAt },
|
|
14
14
|
])
|
|
15
15
|
)
|
|
16
16
|
return NextResponse.json(safe)
|
|
@@ -33,6 +33,7 @@ export async function POST(req: Request) {
|
|
|
33
33
|
encryptedValue: encryptKey(body.value),
|
|
34
34
|
scope: body.scope || 'global',
|
|
35
35
|
agentIds: body.agentIds || [],
|
|
36
|
+
projectId: typeof body.projectId === 'string' && body.projectId.trim() ? body.projectId.trim() : undefined,
|
|
36
37
|
createdAt: now,
|
|
37
38
|
updatedAt: now,
|
|
38
39
|
}
|
|
@@ -126,6 +126,8 @@ export async function PUT(req: Request) {
|
|
|
126
126
|
settings.taskQualityGateRequireVerification = parseBoolSetting(settings.taskQualityGateRequireVerification, false)
|
|
127
127
|
settings.taskQualityGateRequireArtifact = parseBoolSetting(settings.taskQualityGateRequireArtifact, false)
|
|
128
128
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
129
|
+
settings.taskManagementEnabled = parseBoolSetting(settings.taskManagementEnabled, true)
|
|
130
|
+
settings.projectManagementEnabled = parseBoolSetting(settings.projectManagementEnabled, true)
|
|
129
131
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
130
132
|
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
131
133
|
settings.sessionIdleTimeoutSec = parseIntSetting(
|
package/src/cli/index.js
CHANGED
|
@@ -319,10 +319,50 @@ const COMMAND_GROUPS = [
|
|
|
319
319
|
expectsJsonBody: true,
|
|
320
320
|
defaultBody: { action: 'stop-local' },
|
|
321
321
|
}),
|
|
322
|
+
cmd('deploy-local-restart', 'POST', '/openclaw/deploy', 'Restart the managed local OpenClaw deployment (use --data JSON for port/token overrides)', {
|
|
323
|
+
expectsJsonBody: true,
|
|
324
|
+
defaultBody: { action: 'restart-local' },
|
|
325
|
+
}),
|
|
322
326
|
cmd('deploy-bundle', 'POST', '/openclaw/deploy', 'Generate an OpenClaw remote deployment bundle (use --data JSON for template/target/token)', {
|
|
323
327
|
expectsJsonBody: true,
|
|
324
328
|
defaultBody: { action: 'bundle' },
|
|
325
329
|
}),
|
|
330
|
+
cmd('deploy-ssh', 'POST', '/openclaw/deploy', 'Push the official-image OpenClaw bundle to a remote host over SSH (use --data JSON for target/ssh/provider)', {
|
|
331
|
+
expectsJsonBody: true,
|
|
332
|
+
defaultBody: { action: 'ssh-deploy' },
|
|
333
|
+
}),
|
|
334
|
+
cmd('deploy-verify', 'POST', '/openclaw/deploy', 'Verify an OpenClaw endpoint/token pair (use --data JSON for endpoint/token)', {
|
|
335
|
+
expectsJsonBody: true,
|
|
336
|
+
defaultBody: { action: 'verify' },
|
|
337
|
+
}),
|
|
338
|
+
cmd('remote-start', 'POST', '/openclaw/deploy', 'Start a remote SSH-managed OpenClaw stack', {
|
|
339
|
+
expectsJsonBody: true,
|
|
340
|
+
defaultBody: { action: 'remote-start' },
|
|
341
|
+
}),
|
|
342
|
+
cmd('remote-stop', 'POST', '/openclaw/deploy', 'Stop a remote SSH-managed OpenClaw stack', {
|
|
343
|
+
expectsJsonBody: true,
|
|
344
|
+
defaultBody: { action: 'remote-stop' },
|
|
345
|
+
}),
|
|
346
|
+
cmd('remote-restart', 'POST', '/openclaw/deploy', 'Restart a remote SSH-managed OpenClaw stack', {
|
|
347
|
+
expectsJsonBody: true,
|
|
348
|
+
defaultBody: { action: 'remote-restart' },
|
|
349
|
+
}),
|
|
350
|
+
cmd('remote-upgrade', 'POST', '/openclaw/deploy', 'Upgrade a remote SSH-managed OpenClaw stack', {
|
|
351
|
+
expectsJsonBody: true,
|
|
352
|
+
defaultBody: { action: 'remote-upgrade' },
|
|
353
|
+
}),
|
|
354
|
+
cmd('remote-backup', 'POST', '/openclaw/deploy', 'Create a remote backup on an SSH-managed OpenClaw host', {
|
|
355
|
+
expectsJsonBody: true,
|
|
356
|
+
defaultBody: { action: 'remote-backup' },
|
|
357
|
+
}),
|
|
358
|
+
cmd('remote-restore', 'POST', '/openclaw/deploy', 'Restore a remote backup on an SSH-managed OpenClaw host', {
|
|
359
|
+
expectsJsonBody: true,
|
|
360
|
+
defaultBody: { action: 'remote-restore' },
|
|
361
|
+
}),
|
|
362
|
+
cmd('remote-rotate-token', 'POST', '/openclaw/deploy', 'Rotate the gateway token on an SSH-managed OpenClaw host', {
|
|
363
|
+
expectsJsonBody: true,
|
|
364
|
+
defaultBody: { action: 'remote-rotate-token' },
|
|
365
|
+
}),
|
|
326
366
|
cmd('directory', 'GET', '/openclaw/directory', 'List directory entries from running OpenClaw connectors'),
|
|
327
367
|
cmd('gateway-status', 'GET', '/openclaw/gateway', 'Check OpenClaw gateway connection status'),
|
|
328
368
|
cmd('gateway', 'POST', '/openclaw/gateway', 'Call OpenClaw gateway RPC/control action', { expectsJsonBody: true }),
|
package/src/cli/index.test.js
CHANGED
|
@@ -197,6 +197,74 @@ test('openclaw deploy bundle command merges action with provided JSON body', asy
|
|
|
197
197
|
assert.equal(stderr.toString(), '')
|
|
198
198
|
})
|
|
199
199
|
|
|
200
|
+
test('openclaw deploy ssh command merges action with provided JSON body', async () => {
|
|
201
|
+
const stdout = makeWritable()
|
|
202
|
+
const stderr = makeWritable()
|
|
203
|
+
const calls = []
|
|
204
|
+
|
|
205
|
+
const fetchImpl = async (url, init) => {
|
|
206
|
+
calls.push({ url: String(url), init })
|
|
207
|
+
return jsonResponse({ ok: true, processId: 'remote-1' })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const exitCode = await runCli(
|
|
211
|
+
['openclaw', 'deploy-ssh', '--data', '{"target":"openclaw.example.com","ssh":{"host":"1.2.3.4"}}', '--json'],
|
|
212
|
+
{
|
|
213
|
+
fetchImpl,
|
|
214
|
+
stdout,
|
|
215
|
+
stderr,
|
|
216
|
+
env: {},
|
|
217
|
+
cwd: process.cwd(),
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert.equal(exitCode, 0)
|
|
222
|
+
assert.equal(calls.length, 1)
|
|
223
|
+
assert.match(calls[0].url, /\/api\/openclaw\/deploy$/)
|
|
224
|
+
assert.equal(calls[0].init.method, 'POST')
|
|
225
|
+
assert.deepEqual(JSON.parse(String(calls[0].init.body)), {
|
|
226
|
+
action: 'ssh-deploy',
|
|
227
|
+
target: 'openclaw.example.com',
|
|
228
|
+
ssh: { host: '1.2.3.4' },
|
|
229
|
+
})
|
|
230
|
+
assert.equal(stdout.toString().trim(), '{"ok":true,"processId":"remote-1"}')
|
|
231
|
+
assert.equal(stderr.toString(), '')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('openclaw remote restore command merges action with provided JSON body', async () => {
|
|
235
|
+
const stdout = makeWritable()
|
|
236
|
+
const stderr = makeWritable()
|
|
237
|
+
const calls = []
|
|
238
|
+
|
|
239
|
+
const fetchImpl = async (url, init) => {
|
|
240
|
+
calls.push({ url: String(url), init })
|
|
241
|
+
return jsonResponse({ ok: true, remote: { status: 'running' } })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const exitCode = await runCli(
|
|
245
|
+
['openclaw', 'remote-restore', '--data', '{"backupPath":"/opt/openclaw/backups/latest.tgz","ssh":{"host":"1.2.3.4"}}', '--json'],
|
|
246
|
+
{
|
|
247
|
+
fetchImpl,
|
|
248
|
+
stdout,
|
|
249
|
+
stderr,
|
|
250
|
+
env: {},
|
|
251
|
+
cwd: process.cwd(),
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assert.equal(exitCode, 0)
|
|
256
|
+
assert.equal(calls.length, 1)
|
|
257
|
+
assert.match(calls[0].url, /\/api\/openclaw\/deploy$/)
|
|
258
|
+
assert.equal(calls[0].init.method, 'POST')
|
|
259
|
+
assert.deepEqual(JSON.parse(String(calls[0].init.body)), {
|
|
260
|
+
action: 'remote-restore',
|
|
261
|
+
backupPath: '/opt/openclaw/backups/latest.tgz',
|
|
262
|
+
ssh: { host: '1.2.3.4' },
|
|
263
|
+
})
|
|
264
|
+
assert.equal(stdout.toString().trim(), '{"ok":true,"remote":{"status":"running"}}')
|
|
265
|
+
assert.equal(stderr.toString(), '')
|
|
266
|
+
})
|
|
267
|
+
|
|
200
268
|
test('runCli falls back to platform-api-key.txt when env key is missing', async () => {
|
|
201
269
|
const stdout = makeWritable()
|
|
202
270
|
const stderr = makeWritable()
|
package/src/cli/spec.js
CHANGED
|
@@ -224,12 +224,72 @@ const COMMAND_GROUPS = {
|
|
|
224
224
|
path: '/openclaw/deploy',
|
|
225
225
|
staticBody: { action: 'stop-local' },
|
|
226
226
|
},
|
|
227
|
+
'deploy-local-restart': {
|
|
228
|
+
description: 'Restart the managed local OpenClaw deployment (use --data JSON for port/token overrides)',
|
|
229
|
+
method: 'POST',
|
|
230
|
+
path: '/openclaw/deploy',
|
|
231
|
+
staticBody: { action: 'restart-local' },
|
|
232
|
+
},
|
|
227
233
|
'deploy-bundle': {
|
|
228
234
|
description: 'Generate an OpenClaw remote deployment bundle (use --data JSON for template/target/token)',
|
|
229
235
|
method: 'POST',
|
|
230
236
|
path: '/openclaw/deploy',
|
|
231
237
|
staticBody: { action: 'bundle' },
|
|
232
238
|
},
|
|
239
|
+
'deploy-ssh': {
|
|
240
|
+
description: 'Push the official-image OpenClaw bundle to a remote host over SSH (use --data JSON for target/ssh/provider)',
|
|
241
|
+
method: 'POST',
|
|
242
|
+
path: '/openclaw/deploy',
|
|
243
|
+
staticBody: { action: 'ssh-deploy' },
|
|
244
|
+
},
|
|
245
|
+
'deploy-verify': {
|
|
246
|
+
description: 'Verify an OpenClaw endpoint/token pair (use --data JSON for endpoint/token)',
|
|
247
|
+
method: 'POST',
|
|
248
|
+
path: '/openclaw/deploy',
|
|
249
|
+
staticBody: { action: 'verify' },
|
|
250
|
+
},
|
|
251
|
+
'remote-start': {
|
|
252
|
+
description: 'Start a remote SSH-managed OpenClaw stack',
|
|
253
|
+
method: 'POST',
|
|
254
|
+
path: '/openclaw/deploy',
|
|
255
|
+
staticBody: { action: 'remote-start' },
|
|
256
|
+
},
|
|
257
|
+
'remote-stop': {
|
|
258
|
+
description: 'Stop a remote SSH-managed OpenClaw stack',
|
|
259
|
+
method: 'POST',
|
|
260
|
+
path: '/openclaw/deploy',
|
|
261
|
+
staticBody: { action: 'remote-stop' },
|
|
262
|
+
},
|
|
263
|
+
'remote-restart': {
|
|
264
|
+
description: 'Restart a remote SSH-managed OpenClaw stack',
|
|
265
|
+
method: 'POST',
|
|
266
|
+
path: '/openclaw/deploy',
|
|
267
|
+
staticBody: { action: 'remote-restart' },
|
|
268
|
+
},
|
|
269
|
+
'remote-upgrade': {
|
|
270
|
+
description: 'Upgrade a remote SSH-managed OpenClaw stack',
|
|
271
|
+
method: 'POST',
|
|
272
|
+
path: '/openclaw/deploy',
|
|
273
|
+
staticBody: { action: 'remote-upgrade' },
|
|
274
|
+
},
|
|
275
|
+
'remote-backup': {
|
|
276
|
+
description: 'Create a remote backup on an SSH-managed OpenClaw host',
|
|
277
|
+
method: 'POST',
|
|
278
|
+
path: '/openclaw/deploy',
|
|
279
|
+
staticBody: { action: 'remote-backup' },
|
|
280
|
+
},
|
|
281
|
+
'remote-restore': {
|
|
282
|
+
description: 'Restore a remote backup on an SSH-managed OpenClaw host',
|
|
283
|
+
method: 'POST',
|
|
284
|
+
path: '/openclaw/deploy',
|
|
285
|
+
staticBody: { action: 'remote-restore' },
|
|
286
|
+
},
|
|
287
|
+
'remote-rotate-token': {
|
|
288
|
+
description: 'Rotate the gateway token on an SSH-managed OpenClaw host',
|
|
289
|
+
method: 'POST',
|
|
290
|
+
path: '/openclaw/deploy',
|
|
291
|
+
staticBody: { action: 'remote-rotate-token' },
|
|
292
|
+
},
|
|
233
293
|
directory: { description: 'List directory entries from running OpenClaw connectors', method: 'GET', path: '/openclaw/directory' },
|
|
234
294
|
'gateway-status': { description: 'Check OpenClaw gateway connection status', method: 'GET', path: '/openclaw/gateway' },
|
|
235
295
|
gateway: { description: 'Call OpenClaw gateway RPC/control action', method: 'POST', path: '/openclaw/gateway' },
|