From ec9ba456286f35d09c5c7ac138ff75526ef8f3a1 Mon Sep 17 00:00:00 2001 From: nyk <93952610+0xNyk@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:57:17 +0700 Subject: [PATCH] feat: provision full OpenClaw workspaces from agent creation --- src/app/api/agents/route.ts | 41 ++++++++++++++++++++- src/components/panels/agent-detail-tabs.tsx | 13 +++++++ src/lib/validation.ts | 3 ++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts index 3f835f0..1f52638 100644 --- a/src/app/api/agents/route.ts +++ b/src/app/api/agents/route.ts @@ -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 = { ...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, diff --git a/src/components/panels/agent-detail-tabs.tsx b/src/components/panels/agent-detail-tabs.tsx index ee1973b..1e6bee1 100644 --- a/src/components/panels/agent-detail-tabs.tsx +++ b/src/components/panels/agent-detail-tabs.tsx @@ -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(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({ /> Add to gateway config (openclaw.json) + + )} diff --git a/src/lib/validation.ts b/src/lib/validation.ts index f26e7a5..fb13b93 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -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({