82e8c8905b
* feat: replace agent_type with agent_profile_id - Replace agent_type field with agent_profile_id across backend and frontend - Add ProfileResolver interface and StoreProfileResolver implementation - Update task creation/update to use agent_profile_id - Change default agent runtime from docker to standalone - Fix workspace state types to include all required fields - Fix websocket handlers type mismatches for environments, executors, workspaces - Update integration tests to use agent_profile_id * feat: pass model as CLI argument to agents - Add ModelFlag field to AgentTypeConfig for specifying the CLI flag - Set ModelFlag to '--model' for all agents (auggie, claude, codex, gemini, opencode, copilot) - Update launchStandalone to append model flag to agent command - Update launchDocker/buildContainerConfig to append model flag to container command - Model is passed when both agent supports ModelFlag and profile has Model configured * fix: make WebSocket message handling async to prevent blocking When a long-running handler like orchestrator.prompt was executing, it blocked the ReadPump loop, preventing other requests (like workspace.tree.get) from being processed until the handler completed. Now each message is handled in its own goroutine, allowing concurrent message handling so file tree requests work while the agent is working. * feat: show task description as first message in chat - Add 'Task' label with distinct styling (lighter background, border) - Pass taskDescription from page to TaskChatPanel component - Differentiate from regular user messages visually
137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
'use client';
|
|
|
|
import { Suspense, useState } from 'react';
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import { IconCloud, IconServer } from '@tabler/icons-react';
|
|
import { Button } from '@kandev/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@kandev/ui/card';
|
|
import { Input } from '@kandev/ui/input';
|
|
import { Label } from '@kandev/ui/label';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
|
import { Separator } from '@kandev/ui/separator';
|
|
import { createExecutorAction } from '@/app/actions/executors';
|
|
import { getWebSocketClient } from '@/lib/ws/connection';
|
|
|
|
const EXECUTOR_TYPES = ['local_docker', 'remote_docker'] as const;
|
|
type ExecutorType = (typeof EXECUTOR_TYPES)[number];
|
|
|
|
export default function ExecutorCreatePage() {
|
|
return (
|
|
<Suspense fallback={<div className="p-4">Loading...</div>}>
|
|
<ExecutorCreatePageContent />
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
function ExecutorCreatePageContent() {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const initialType = searchParams.get('type');
|
|
const [type, setType] = useState<ExecutorType>(() => {
|
|
if (EXECUTOR_TYPES.includes(initialType as ExecutorType)) {
|
|
return initialType as ExecutorType;
|
|
}
|
|
return 'local_docker';
|
|
});
|
|
const [name, setName] = useState('Local Docker');
|
|
const [dockerHost, setDockerHost] = useState('unix:///var/run/docker.sock');
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
|
|
const handleCreate = async () => {
|
|
if (type !== 'local_docker') {
|
|
return;
|
|
}
|
|
setIsCreating(true);
|
|
try {
|
|
const payload = {
|
|
name,
|
|
type,
|
|
status: 'active',
|
|
config: { docker_host: dockerHost },
|
|
};
|
|
const client = getWebSocketClient();
|
|
if (client) {
|
|
await client.request('executor.create', payload);
|
|
} else {
|
|
await createExecutorAction(payload);
|
|
}
|
|
router.push('/settings/executors');
|
|
} finally {
|
|
setIsCreating(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<div>
|
|
<h2 className="text-2xl font-bold">Create Executor</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
Choose an executor type to run environments on your infrastructure.
|
|
</p>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
{type === 'local_docker' ? <IconServer className="h-4 w-4" /> : <IconCloud className="h-4 w-4" />}
|
|
{type === 'local_docker' ? 'Local Docker Executor' : 'Remote Docker Executor'}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{type === 'local_docker'
|
|
? 'Uses the local Docker daemon on this machine.'
|
|
: 'Connects to a remote Docker host (coming soon).'}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="executor-type">Executor type</Label>
|
|
<Select value={type} onValueChange={(value) => setType(value as ExecutorType)}>
|
|
<SelectTrigger id="executor-type">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="local_docker">Local Docker</SelectItem>
|
|
<SelectItem value="remote_docker" disabled>
|
|
Remote Docker (coming soon)
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="executor-name">Executor name</Label>
|
|
<Input
|
|
id="executor-name"
|
|
value={name}
|
|
onChange={(event) => setName(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="docker-host">Docker host env value</Label>
|
|
<Input
|
|
id="docker-host"
|
|
value={dockerHost}
|
|
onChange={(event) => setDockerHost(event.target.value)}
|
|
placeholder="unix:///var/run/docker.sock"
|
|
disabled={type !== 'local_docker'}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Repositories will be mounted as volumes at runtime.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="flex items-center justify-end gap-2">
|
|
<Button variant="outline" onClick={() => router.push('/settings/executors')}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleCreate} disabled={isCreating || type !== 'local_docker'}>
|
|
{isCreating ? 'Creating...' : 'Create Executor'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|