feat: provision full OpenClaw workspaces from agent creation

This commit is contained in:
nyk 2026-03-05 10:57:17 +07:00 committed by GitHub
parent d59b2e70a1
commit ec9ba45628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 2 deletions

View File

@ -8,6 +8,10 @@ import { requireRole } from '@/lib/auth';
import { mutationLimiter } from '@/lib/rate-limit';
import { logger } from '@/lib/logger';
import { validateBody, createAgentSchema } from '@/lib/validation';
import { runOpenClaw } from '@/lib/command';
import { config as appConfig } from '@/lib/config';
import { resolveWithin } from '@/lib/paths';
import path from 'node:path';
/**
* GET /api/agents - List all agents with optional filtering
@ -123,6 +127,7 @@ export async function POST(request: NextRequest) {
const {
name,
openclaw_id,
role,
session_key,
soul_content,
@ -130,9 +135,16 @@ export async function POST(request: NextRequest) {
config = {},
template,
gateway_config,
write_to_gateway
write_to_gateway,
provision_openclaw_workspace,
openclaw_workspace_path
} = body;
const openclawId = (openclaw_id || name || 'agent')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
// Resolve template if specified
let finalRole = role;
let finalConfig: Record<string, any> = { ...config };
@ -158,6 +170,32 @@ export async function POST(request: NextRequest) {
if (existingAgent) {
return NextResponse.json({ error: 'Agent name already exists' }, { status: 409 });
}
if (provision_openclaw_workspace) {
if (!appConfig.openclawStateDir) {
return NextResponse.json(
{ error: 'OPENCLAW_STATE_DIR is not configured; cannot provision OpenClaw workspace' },
{ status: 500 }
);
}
const workspacePath = openclaw_workspace_path
? path.resolve(openclaw_workspace_path)
: resolveWithin(appConfig.openclawStateDir, path.join('workspaces', openclawId));
try {
await runOpenClaw(
['agents', 'add', openclawId, '--name', name, '--workspace', workspacePath, '--non-interactive'],
{ timeoutMs: 20000 }
);
} catch (provisionError: any) {
logger.error({ err: provisionError, openclawId, workspacePath }, 'OpenClaw workspace provisioning failed');
return NextResponse.json(
{ error: provisionError?.message || 'Failed to provision OpenClaw agent workspace' },
{ status: 502 }
);
}
}
const now = Math.floor(Date.now() / 1000);
@ -215,7 +253,6 @@ export async function POST(request: NextRequest) {
// Write to gateway config if requested
if (write_to_gateway && finalConfig) {
try {
const openclawId = (name || 'agent').toLowerCase().replace(/\s+/g, '-');
await writeAgentToConfig({
id: openclawId,
name,

View File

@ -852,6 +852,7 @@ export function CreateAgentModal({
dockerNetwork: 'none' as 'none' | 'bridge',
session_key: '',
write_to_gateway: true,
provision_openclaw_workspace: true,
})
const [isCreating, setIsCreating] = useState(false)
const [error, setError] = useState<string | null>(null)
@ -916,10 +917,12 @@ export function CreateAgentModal({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: formData.name,
openclaw_id: formData.id || undefined,
role: formData.role,
session_key: formData.session_key || undefined,
template: selectedTemplate || undefined,
write_to_gateway: formData.write_to_gateway,
provision_openclaw_workspace: formData.provision_openclaw_workspace,
gateway_config: {
model: { primary: primaryModel },
identity: { name: formData.name, theme: formData.role, emoji: formData.emoji },
@ -1199,6 +1202,16 @@ export function CreateAgentModal({
/>
<span className="text-sm text-foreground">Add to gateway config (openclaw.json)</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={formData.provision_openclaw_workspace}
onChange={(e) => setFormData(prev => ({ ...prev, provision_openclaw_workspace: e.target.checked }))}
className="w-4 h-4 rounded border-border"
/>
<span className="text-sm text-foreground">Provision full OpenClaw workspace (`openclaw agents add`)</span>
</label>
</div>
)}
</div>

View File

@ -45,6 +45,7 @@ export const updateTaskSchema = createTaskSchema.partial()
export const createAgentSchema = z.object({
name: z.string().min(1, 'Name is required').max(100),
openclaw_id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, 'openclaw_id must be kebab-case').max(100).optional(),
role: z.string().min(1, 'Role is required').max(100).optional(),
session_key: z.string().max(200).optional(),
soul_content: z.string().max(50000).optional(),
@ -53,6 +54,8 @@ export const createAgentSchema = z.object({
template: z.string().max(100).optional(),
gateway_config: z.record(z.string(), z.unknown()).optional(),
write_to_gateway: z.boolean().optional(),
provision_openclaw_workspace: z.boolean().optional(),
openclaw_workspace_path: z.string().min(1).max(500).optional(),
})
export const bulkUpdateTaskStatusSchema = z.object({