feat: provision full OpenClaw workspaces from agent creation
This commit is contained in:
parent
d59b2e70a1
commit
ec9ba45628
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue