Checkpoint: Phase 7 complete: Orchestrator Agent добавлен в /agents с меткой CROWN/SYSTEM, кнопками Configure и Open Chat. /chat читает конфиг оркестратора из БД (модель, промпт, инструменты). AgentDetailModal поддерживает isOrchestrator. 24 теста пройдены.
This commit is contained in:
@@ -36,6 +36,11 @@ import {
|
||||
Loader2,
|
||||
AlertCircle,
|
||||
BarChart2,
|
||||
Crown,
|
||||
MessageSquare,
|
||||
Shield,
|
||||
Wrench,
|
||||
Code2,
|
||||
} from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
@@ -50,6 +55,10 @@ const ROLE_ICONS: Record<string, any> = {
|
||||
researcher: Brain,
|
||||
executor: Zap,
|
||||
monitor: Eye,
|
||||
orchestrator: Crown,
|
||||
browser: Globe,
|
||||
tool_builder: Wrench,
|
||||
agent_compiler: Code2,
|
||||
};
|
||||
|
||||
function getStatusConfig(status: string) {
|
||||
@@ -130,7 +139,7 @@ export default function Agents() {
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground">Agent Fleet</h2>
|
||||
<p className="text-sm text-muted-foreground font-mono mt-1">
|
||||
{agents.length} total agents
|
||||
{agents.filter((a: any) => !a.isOrchestrator).length} agents · {agents.filter((a: any) => a.isOrchestrator).length} orchestrator
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -162,83 +171,63 @@ export default function Agents() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
/* Agent cards grid */
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{agents.map((agent: any, i: number) => {
|
||||
const sc = getStatusConfig(agent.status || "idle");
|
||||
const Icon = ROLE_ICONS[agent.role] || Bot;
|
||||
const temperature = typeof agent.temperature === "string" ? parseFloat(agent.temperature) : agent.temperature;
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Orchestrator Section */}
|
||||
{agents.filter((a: any) => a.isOrchestrator).map((agent: any) => {
|
||||
const temperature = typeof agent.temperature === "string" ? parseFloat(agent.temperature) : (agent.temperature ?? 0.7);
|
||||
return (
|
||||
<motion.div
|
||||
key={agent.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
>
|
||||
<Card className={`bg-card border-border/50 hover:border-primary/30 transition-all cursor-pointer ${sc.glow}`} onClick={() => handleEditAgent(agent)}>
|
||||
<motion.div key={agent.id} initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
|
||||
<Card className="bg-card border-cyan-500/40 hover:border-cyan-500/70 transition-all glow-cyan">
|
||||
<CardContent className="p-5">
|
||||
{/* Top row */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-secondary/50 border border-border/50 flex items-center justify-center">
|
||||
<Icon className={`w-5 h-5 ${sc.color}`} />
|
||||
<div className="w-12 h-12 rounded-lg bg-cyan-500/15 border border-cyan-500/40 flex items-center justify-center">
|
||||
<Crown className="w-6 h-6 text-cyan-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-foreground">{agent.name}</h3>
|
||||
<p className="text-[11px] text-muted-foreground mt-0.5">{agent.description || "No description"}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-base font-bold text-foreground">{agent.name}</h3>
|
||||
<Badge className="text-[9px] font-mono bg-cyan-500/20 text-cyan-300 border-cyan-500/40 px-1.5 py-0">
|
||||
<Crown className="w-2.5 h-2.5 mr-1" /> ORCHESTRATOR
|
||||
</Badge>
|
||||
<Badge className="text-[9px] font-mono bg-amber-500/20 text-amber-300 border-amber-500/40 px-1.5 py-0">
|
||||
<Shield className="w-2.5 h-2.5 mr-1" /> SYSTEM
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mt-0.5">{agent.description || "Main orchestrator — controls all agents, tools and system resources"}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className={`text-[10px] font-mono ${sc.badge}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${sc.bg} mr-1.5 ${agent.isActive ? "pulse-indicator" : ""}`} />
|
||||
{agent.isActive ? "ACTIVE" : "INACTIVE"}
|
||||
<Badge variant="outline" className="text-[10px] font-mono bg-neon-green/15 text-neon-green border-neon-green/30">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-neon-green mr-1.5 pulse-indicator" />
|
||||
ACTIVE
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Model & Node info */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
<div className="p-2.5 rounded-md bg-secondary/30 border border-border/20">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<Brain className="w-3 h-3 text-primary" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono">MODEL</span>
|
||||
</div>
|
||||
<div className="text-xs font-mono font-medium text-primary">{agent.model}</div>
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
<div className="p-2.5 rounded-md bg-cyan-500/10 border border-cyan-500/20">
|
||||
<div className="text-[10px] text-muted-foreground font-mono mb-1">MODEL</div>
|
||||
<div className="text-xs font-mono font-bold text-cyan-400">{agent.model}</div>
|
||||
<div className="text-[10px] font-mono text-muted-foreground">{agent.provider}</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-md bg-secondary/30 border border-border/20">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<Cpu className="w-3 h-3 text-primary" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono">CONFIG</span>
|
||||
</div>
|
||||
<div className="text-xs font-mono font-medium text-foreground">T: {temperature.toFixed(2)}</div>
|
||||
<div className="text-[10px] text-muted-foreground font-mono mb-1">TEMPERATURE</div>
|
||||
<div className="text-xs font-mono font-bold text-foreground">{temperature.toFixed(2)}</div>
|
||||
<div className="text-[10px] font-mono text-muted-foreground">Tokens: {agent.maxTokens}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Metrics row */}
|
||||
<div className="flex items-center gap-4 mb-4 text-[10px] font-mono">
|
||||
<div className="flex items-center gap-1">
|
||||
<Zap className="w-3 h-3 text-neon-amber" />
|
||||
<span className="text-muted-foreground">Role:</span>
|
||||
<span className="text-foreground font-medium capitalize">{agent.role}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3 text-primary" />
|
||||
<span className="text-muted-foreground">Created:</span>
|
||||
<span className="text-foreground font-medium">{new Date(agent.createdAt).toLocaleDateString()}</span>
|
||||
<div className="p-2.5 rounded-md bg-secondary/30 border border-border/20">
|
||||
<div className="text-[10px] text-muted-foreground font-mono mb-1">TOOLS</div>
|
||||
<div className="text-xs font-mono font-bold text-foreground">
|
||||
{agent.allowedTools?.length ?? 0} tools
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-muted-foreground">Full access</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tools */}
|
||||
{agent.allowedTools && agent.allowedTools.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<span className="text-[10px] text-muted-foreground font-mono block mb-1.5">TOOLS</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{agent.allowedTools.map((tool: string) => (
|
||||
<span
|
||||
key={tool}
|
||||
className="px-2 py-0.5 rounded text-[10px] font-mono bg-primary/10 text-primary border border-primary/20"
|
||||
>
|
||||
<span key={tool} className="px-2 py-0.5 rounded text-[10px] font-mono bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">
|
||||
{tool}
|
||||
</span>
|
||||
))}
|
||||
@@ -246,47 +235,167 @@ export default function Agents() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 pt-3 border-t border-border/30">
|
||||
<div className="flex items-center gap-2 pt-3 border-t border-cyan-500/20">
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-7 text-[11px] bg-cyan-500/15 text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/25"
|
||||
onClick={(e) => { e.stopPropagation(); navigate("/chat"); }}
|
||||
>
|
||||
<MessageSquare className="w-3 h-3 mr-1" /> Open Chat
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-primary border-primary/30 hover:bg-primary/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditAgent(agent);
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); handleEditAgent(agent); }}
|
||||
>
|
||||
Edit
|
||||
Configure
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-neon-amber border-neon-amber/30 hover:bg-neon-amber/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/agents/${agent.id}/metrics`);
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); navigate(`/agents/${agent.id}/metrics`); }}
|
||||
>
|
||||
<BarChart2 className="w-3 h-3 mr-1" /> Metrics
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-neon-red border-neon-red/30 hover:bg-neon-red/10 ml-auto"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteClick(agent.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-3 h-3 mr-1" /> Delete
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Specialized Agents Section */}
|
||||
{agents.filter((a: any) => !a.isOrchestrator).length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-muted-foreground font-mono mb-3 flex items-center gap-2">
|
||||
<Bot className="w-4 h-4" /> SPECIALIZED AGENTS
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{agents.filter((a: any) => !a.isOrchestrator).map((agent: any, i: number) => {
|
||||
const sc = getStatusConfig(agent.status || "idle");
|
||||
const Icon = ROLE_ICONS[agent.role] || Bot;
|
||||
const temperature = typeof agent.temperature === "string" ? parseFloat(agent.temperature) : (agent.temperature ?? 0.7);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={agent.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: i * 0.08 }}
|
||||
>
|
||||
<Card className={`bg-card border-border/50 hover:border-primary/30 transition-all cursor-pointer ${sc.glow}`} onClick={() => handleEditAgent(agent)}>
|
||||
<CardContent className="p-5">
|
||||
{/* Top row */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-secondary/50 border border-border/50 flex items-center justify-center">
|
||||
<Icon className={`w-5 h-5 ${sc.color}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<h3 className="text-sm font-semibold text-foreground">{agent.name}</h3>
|
||||
{agent.isSystem && (
|
||||
<Badge className="text-[9px] font-mono bg-amber-500/15 text-amber-400 border-amber-500/30 px-1 py-0">
|
||||
SYS
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mt-0.5">{agent.description || "No description"}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className={`text-[10px] font-mono ${sc.badge}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${sc.bg} mr-1.5 ${agent.isActive ? "pulse-indicator" : ""}`} />
|
||||
{agent.isActive ? "ACTIVE" : "INACTIVE"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Model & Config */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
<div className="p-2.5 rounded-md bg-secondary/30 border border-border/20">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<Brain className="w-3 h-3 text-primary" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono">MODEL</span>
|
||||
</div>
|
||||
<div className="text-xs font-mono font-medium text-primary">{agent.model}</div>
|
||||
<div className="text-[10px] font-mono text-muted-foreground">{agent.provider}</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-md bg-secondary/30 border border-border/20">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<Cpu className="w-3 h-3 text-primary" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono">CONFIG</span>
|
||||
</div>
|
||||
<div className="text-xs font-mono font-medium text-foreground">T: {temperature.toFixed(2)}</div>
|
||||
<div className="text-[10px] font-mono text-muted-foreground">Tokens: {agent.maxTokens}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Role & Date */}
|
||||
<div className="flex items-center gap-4 mb-4 text-[10px] font-mono">
|
||||
<div className="flex items-center gap-1">
|
||||
<Zap className="w-3 h-3 text-neon-amber" />
|
||||
<span className="text-muted-foreground">Role:</span>
|
||||
<span className="text-foreground font-medium capitalize">{agent.role}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3 text-primary" />
|
||||
<span className="text-muted-foreground">Created:</span>
|
||||
<span className="text-foreground font-medium">{new Date(agent.createdAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tools */}
|
||||
{agent.allowedTools && agent.allowedTools.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<span className="text-[10px] text-muted-foreground font-mono block mb-1.5">TOOLS</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{agent.allowedTools.map((tool: string) => (
|
||||
<span key={tool} className="px-2 py-0.5 rounded text-[10px] font-mono bg-primary/10 text-primary border border-primary/20">
|
||||
{tool}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 pt-3 border-t border-border/30">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-primary border-primary/30 hover:bg-primary/10"
|
||||
onClick={(e) => { e.stopPropagation(); handleEditAgent(agent); }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-neon-amber border-neon-amber/30 hover:bg-neon-amber/10"
|
||||
onClick={(e) => { e.stopPropagation(); navigate(`/agents/${agent.id}/metrics`); }}
|
||||
>
|
||||
<BarChart2 className="w-3 h-3 mr-1" /> Metrics
|
||||
</Button>
|
||||
{!agent.isSystem && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 text-[11px] text-neon-red border-neon-red/30 hover:bg-neon-red/10 ml-auto"
|
||||
onClick={(e) => { e.stopPropagation(); handleDeleteClick(agent.id); }}
|
||||
>
|
||||
<Trash2 className="w-3 h-3 mr-1" /> Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -237,15 +237,7 @@ function MessageBubble({ msg }: { msg: ChatMessage }) {
|
||||
// ─── Main Chat Component ──────────────────────────────────────────────────────
|
||||
|
||||
export default function Chat() {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([
|
||||
{
|
||||
id: "welcome",
|
||||
role: "system",
|
||||
content:
|
||||
"GoClaw Orchestrator ready.\nI have access to all agents, tools, and skills.\nType a command or ask anything.",
|
||||
timestamp: new Date().toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" }),
|
||||
},
|
||||
]);
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [conversationHistory, setConversationHistory] = useState<
|
||||
Array<{ role: "user" | "assistant" | "system"; content: string }>
|
||||
>([]);
|
||||
@@ -256,6 +248,22 @@ export default function Chat() {
|
||||
|
||||
const agentsQuery = trpc.agents.list.useQuery(undefined, { refetchInterval: 30000 });
|
||||
const orchestratorMutation = trpc.orchestrator.chat.useMutation();
|
||||
const orchestratorConfigQuery = trpc.orchestrator.getConfig.useQuery();
|
||||
|
||||
// Initialize welcome message with orchestrator name from DB
|
||||
useEffect(() => {
|
||||
if (orchestratorConfigQuery.data && messages.length === 0) {
|
||||
const cfg = orchestratorConfigQuery.data;
|
||||
setMessages([
|
||||
{
|
||||
id: "welcome",
|
||||
role: "system",
|
||||
content: `${cfg.name} ready. Model: ${cfg.model}\nI have access to all agents, tools, and skills.\nType a command or ask anything.`,
|
||||
timestamp: new Date().toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" }),
|
||||
},
|
||||
]);
|
||||
}
|
||||
}, [orchestratorConfigQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
@@ -364,7 +372,8 @@ export default function Chat() {
|
||||
};
|
||||
|
||||
const agents = agentsQuery.data ?? [];
|
||||
const activeAgents = agents.filter((a) => a.isActive);
|
||||
const activeAgents = agents.filter((a) => a.isActive && !(a as any).isOrchestrator);
|
||||
const orchConfig = orchestratorConfigQuery.data;
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col gap-3">
|
||||
@@ -375,27 +384,44 @@ export default function Chat() {
|
||||
<Bot className="w-4 h-4 text-cyan-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-foreground">GoClaw Orchestrator</h2>
|
||||
<h2 className="text-lg font-bold text-foreground">
|
||||
{orchConfig?.name ?? "GoClaw Orchestrator"}
|
||||
</h2>
|
||||
<p className="text-[11px] font-mono text-muted-foreground">
|
||||
Main AI · {activeAgents.length} agents · {ORCHESTRATOR_TOOLS_COUNT} tools
|
||||
{orchConfig ? (
|
||||
<span>
|
||||
<span className="text-cyan-400/80">{orchConfig.model}</span>
|
||||
{" · "}{activeAgents.length} agents · {ORCHESTRATOR_TOOLS_COUNT} tools
|
||||
</span>
|
||||
) : (
|
||||
`Main AI · ${activeAgents.length} agents · ${ORCHESTRATOR_TOOLS_COUNT} tools`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active agents badges */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap justify-end">
|
||||
{activeAgents.slice(0, 4).map((agent) => (
|
||||
<Badge
|
||||
key={agent.id}
|
||||
variant="outline"
|
||||
className="text-[9px] h-5 px-1.5 font-mono border-border/50 text-muted-foreground"
|
||||
>
|
||||
{agent.role === "browser" && <Globe className="w-2.5 h-2.5 mr-1 text-cyan-400" />}
|
||||
{agent.role === "tool_builder" && <Wrench className="w-2.5 h-2.5 mr-1 text-orange-400" />}
|
||||
{agent.role === "agent_compiler" && <Cpu className="w-2.5 h-2.5 mr-1 text-purple-400" />}
|
||||
{agent.name}
|
||||
</Badge>
|
||||
))}
|
||||
{/* Active agents badges + Configure link */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5 flex-wrap justify-end">
|
||||
{activeAgents.slice(0, 3).map((agent) => (
|
||||
<Badge
|
||||
key={agent.id}
|
||||
variant="outline"
|
||||
className="text-[9px] h-5 px-1.5 font-mono border-border/50 text-muted-foreground"
|
||||
>
|
||||
{agent.role === "browser" && <Globe className="w-2.5 h-2.5 mr-1 text-cyan-400" />}
|
||||
{agent.role === "tool_builder" && <Wrench className="w-2.5 h-2.5 mr-1 text-orange-400" />}
|
||||
{agent.role === "agent_compiler" && <Cpu className="w-2.5 h-2.5 mr-1 text-purple-400" />}
|
||||
{agent.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<a
|
||||
href="/agents"
|
||||
className="text-[10px] font-mono px-2 py-1 rounded border border-cyan-500/30 text-cyan-400/70 hover:text-cyan-400 hover:border-cyan-500/60 transition-colors bg-cyan-500/5 shrink-0"
|
||||
>
|
||||
Configure
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
2
drizzle/0003_lazy_hitman.sql
Normal file
2
drizzle/0003_lazy_hitman.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `agents` ADD `isSystem` boolean DEFAULT false;--> statement-breakpoint
|
||||
ALTER TABLE `agents` ADD `isOrchestrator` boolean DEFAULT false;
|
||||
874
drizzle/meta/0003_snapshot.json
Normal file
874
drizzle/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,874 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "19c68417-6ca1-4df0-9f19-89364c61dd84",
|
||||
"prevId": "c2d59f1f-0ab6-4daf-80f8-651cb95a4778",
|
||||
"tables": {
|
||||
"agentAccessControl": {
|
||||
"name": "agentAccessControl",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"agentId": {
|
||||
"name": "agentId",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tool": {
|
||||
"name": "tool",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"isAllowed": {
|
||||
"name": "isAllowed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"maxExecutionsPerHour": {
|
||||
"name": "maxExecutionsPerHour",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 100
|
||||
},
|
||||
"timeoutSeconds": {
|
||||
"name": "timeoutSeconds",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 30
|
||||
},
|
||||
"allowedPatterns": {
|
||||
"name": "allowedPatterns",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"blockedPatterns": {
|
||||
"name": "blockedPatterns",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agentAccessControl_agentId_tool_idx": {
|
||||
"name": "agentAccessControl_agentId_tool_idx",
|
||||
"columns": [
|
||||
"agentId",
|
||||
"tool"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"agentAccessControl_id": {
|
||||
"name": "agentAccessControl_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"agentHistory": {
|
||||
"name": "agentHistory",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"agentId": {
|
||||
"name": "agentId",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"userMessage": {
|
||||
"name": "userMessage",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"agentResponse": {
|
||||
"name": "agentResponse",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"conversationId": {
|
||||
"name": "conversationId",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"messageIndex": {
|
||||
"name": "messageIndex",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "enum('pending','success','error')",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agentHistory_agentId_idx": {
|
||||
"name": "agentHistory_agentId_idx",
|
||||
"columns": [
|
||||
"agentId"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"agentHistory_id": {
|
||||
"name": "agentHistory_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"agentMetrics": {
|
||||
"name": "agentMetrics",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"agentId": {
|
||||
"name": "agentId",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"requestId": {
|
||||
"name": "requestId",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"userMessage": {
|
||||
"name": "userMessage",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"agentResponse": {
|
||||
"name": "agentResponse",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"inputTokens": {
|
||||
"name": "inputTokens",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"outputTokens": {
|
||||
"name": "outputTokens",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"totalTokens": {
|
||||
"name": "totalTokens",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"processingTimeMs": {
|
||||
"name": "processingTimeMs",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "enum('success','error','timeout','rate_limited')",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"errorMessage": {
|
||||
"name": "errorMessage",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"toolsCalled": {
|
||||
"name": "toolsCalled",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"temperature": {
|
||||
"name": "temperature",
|
||||
"type": "decimal(3,2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agentMetrics_agentId_idx": {
|
||||
"name": "agentMetrics_agentId_idx",
|
||||
"columns": [
|
||||
"agentId"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"agentMetrics_createdAt_idx": {
|
||||
"name": "agentMetrics_createdAt_idx",
|
||||
"columns": [
|
||||
"createdAt"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"agentMetrics_id": {
|
||||
"name": "agentMetrics_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {
|
||||
"agentMetrics_requestId_unique": {
|
||||
"name": "agentMetrics_requestId_unique",
|
||||
"columns": [
|
||||
"requestId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"agents": {
|
||||
"name": "agents",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"temperature": {
|
||||
"name": "temperature",
|
||||
"type": "decimal(3,2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'0.7'"
|
||||
},
|
||||
"maxTokens": {
|
||||
"name": "maxTokens",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 2048
|
||||
},
|
||||
"topP": {
|
||||
"name": "topP",
|
||||
"type": "decimal(3,2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'1.0'"
|
||||
},
|
||||
"frequencyPenalty": {
|
||||
"name": "frequencyPenalty",
|
||||
"type": "decimal(3,2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'0.0'"
|
||||
},
|
||||
"presencePenalty": {
|
||||
"name": "presencePenalty",
|
||||
"type": "decimal(3,2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'0.0'"
|
||||
},
|
||||
"systemPrompt": {
|
||||
"name": "systemPrompt",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"allowedTools": {
|
||||
"name": "allowedTools",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"allowedDomains": {
|
||||
"name": "allowedDomains",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"maxRequestsPerHour": {
|
||||
"name": "maxRequestsPerHour",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 100
|
||||
},
|
||||
"isActive": {
|
||||
"name": "isActive",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"isPublic": {
|
||||
"name": "isPublic",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"isSystem": {
|
||||
"name": "isSystem",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"isOrchestrator": {
|
||||
"name": "isOrchestrator",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"tags": {
|
||||
"name": "tags",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('[]')"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "('{}')"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agents_userId_idx": {
|
||||
"name": "agents_userId_idx",
|
||||
"columns": [
|
||||
"userId"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"agents_model_idx": {
|
||||
"name": "agents_model_idx",
|
||||
"columns": [
|
||||
"model"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"agents_id": {
|
||||
"name": "agents_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"browserSessions": {
|
||||
"name": "browserSessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"sessionId": {
|
||||
"name": "sessionId",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"agentId": {
|
||||
"name": "agentId",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"currentUrl": {
|
||||
"name": "currentUrl",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "enum('active','idle','closed','error')",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'idle'"
|
||||
},
|
||||
"screenshotUrl": {
|
||||
"name": "screenshotUrl",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastActionAt": {
|
||||
"name": "lastActionAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"closedAt": {
|
||||
"name": "closedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"browserSessions_id": {
|
||||
"name": "browserSessions_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {
|
||||
"browserSessions_sessionId_unique": {
|
||||
"name": "browserSessions_sessionId_unique",
|
||||
"columns": [
|
||||
"sessionId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"toolDefinitions": {
|
||||
"name": "toolDefinitions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"toolId": {
|
||||
"name": "toolId",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"category": {
|
||||
"name": "category",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'custom'"
|
||||
},
|
||||
"dangerous": {
|
||||
"name": "dangerous",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"parameters": {
|
||||
"name": "parameters",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"implementation": {
|
||||
"name": "implementation",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"isActive": {
|
||||
"name": "isActive",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"createdBy": {
|
||||
"name": "createdBy",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"toolDefinitions_id": {
|
||||
"name": "toolDefinitions_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {
|
||||
"toolDefinitions_toolId_unique": {
|
||||
"name": "toolDefinitions_toolId_unique",
|
||||
"columns": [
|
||||
"toolId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"openId": {
|
||||
"name": "openId",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(320)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"loginMethod": {
|
||||
"name": "loginMethod",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "enum('user','admin')",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'user'"
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
},
|
||||
"lastSignedIn": {
|
||||
"name": "lastSignedIn",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {
|
||||
"users_openId_unique": {
|
||||
"name": "users_openId_unique",
|
||||
"columns": [
|
||||
"openId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"checkConstraint": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"tables": {},
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,13 @@
|
||||
"when": 1774042457262,
|
||||
"tag": "0002_tricky_saracen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "5",
|
||||
"when": 1774043298939,
|
||||
"tag": "0003_lazy_hitman",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -57,6 +57,8 @@ export const agents = mysqlTable("agents", {
|
||||
// Статус
|
||||
isActive: boolean("isActive").default(true),
|
||||
isPublic: boolean("isPublic").default(false),
|
||||
isSystem: boolean("isSystem").default(false), // Системный агент (нельзя удалить)
|
||||
isOrchestrator: boolean("isOrchestrator").default(false), // Главный оркестратор чата
|
||||
|
||||
// Метаданные
|
||||
tags: json("tags").$type<string[]>().default([]),
|
||||
|
||||
@@ -40,7 +40,7 @@ async function main() {
|
||||
CONSTRAINT \`agentMetrics_requestId_unique\` UNIQUE(\`requestId\`)
|
||||
)`,
|
||||
|
||||
// agents
|
||||
// agents — full schema with isSystem and isOrchestrator
|
||||
`CREATE TABLE IF NOT EXISTS \`agents\` (
|
||||
\`id\` int AUTO_INCREMENT NOT NULL,
|
||||
\`userId\` int NOT NULL,
|
||||
@@ -60,6 +60,8 @@ async function main() {
|
||||
\`maxRequestsPerHour\` int DEFAULT 100,
|
||||
\`isActive\` boolean DEFAULT true,
|
||||
\`isPublic\` boolean DEFAULT false,
|
||||
\`isSystem\` boolean DEFAULT false,
|
||||
\`isOrchestrator\` boolean DEFAULT false,
|
||||
\`tags\` json,
|
||||
\`metadata\` json,
|
||||
\`createdAt\` timestamp NOT NULL DEFAULT (now()),
|
||||
@@ -101,6 +103,19 @@ async function main() {
|
||||
CONSTRAINT \`browserSessions_sessionId_unique\` UNIQUE(\`sessionId\`)
|
||||
)`,
|
||||
|
||||
// agentHistory — conversation history per agent
|
||||
`CREATE TABLE IF NOT EXISTS \`agentHistory\` (
|
||||
\`id\` int AUTO_INCREMENT NOT NULL,
|
||||
\`agentId\` int NOT NULL,
|
||||
\`sessionId\` varchar(64),
|
||||
\`role\` enum('user','assistant','system','tool') NOT NULL,
|
||||
\`content\` text NOT NULL,
|
||||
\`toolCalls\` json,
|
||||
\`metadata\` json,
|
||||
\`createdAt\` timestamp NOT NULL DEFAULT (now()),
|
||||
CONSTRAINT \`agentHistory_id\` PRIMARY KEY(\`id\`)
|
||||
)`,
|
||||
|
||||
// Indexes
|
||||
`CREATE INDEX IF NOT EXISTS \`agentAccessControl_agentId_tool_idx\` ON \`agentAccessControl\` (\`agentId\`,\`tool\`)`,
|
||||
`CREATE INDEX IF NOT EXISTS \`agentMetrics_agentId_idx\` ON \`agentMetrics\` (\`agentId\`)`,
|
||||
@@ -108,6 +123,7 @@ async function main() {
|
||||
`CREATE INDEX IF NOT EXISTS \`agents_userId_idx\` ON \`agents\` (\`userId\`)`,
|
||||
`CREATE INDEX IF NOT EXISTS \`agents_model_idx\` ON \`agents\` (\`model\`)`,
|
||||
`CREATE INDEX IF NOT EXISTS \`browserSessions_agentId_idx\` ON \`browserSessions\` (\`agentId\`)`,
|
||||
`CREATE INDEX IF NOT EXISTS \`agentHistory_agentId_idx\` ON \`agentHistory\` (\`agentId\`)`,
|
||||
];
|
||||
|
||||
for (const stmt of statements) {
|
||||
@@ -118,12 +134,36 @@ async function main() {
|
||||
} catch (e) {
|
||||
if (e.code === 'ER_DUP_KEYNAME' || e.message.includes('Duplicate key name')) {
|
||||
console.log('⚠ Index already exists (ok)');
|
||||
} else if (e.message.includes('already exists')) {
|
||||
console.log('⚠ Already exists (ok)');
|
||||
} else {
|
||||
console.error('✗ Error:', e.message.slice(0, 120));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ALTER TABLE to add missing columns to existing tables
|
||||
const alterStatements = [
|
||||
`ALTER TABLE \`agents\` ADD COLUMN IF NOT EXISTS \`isSystem\` boolean DEFAULT false`,
|
||||
`ALTER TABLE \`agents\` ADD COLUMN IF NOT EXISTS \`isOrchestrator\` boolean DEFAULT false`,
|
||||
];
|
||||
|
||||
console.log('\n--- Applying ALTER TABLE migrations ---');
|
||||
for (const stmt of alterStatements) {
|
||||
try {
|
||||
await conn.query(stmt);
|
||||
const col = stmt.match(/ADD COLUMN.*?`(\w+)`/)?.[1] || 'column';
|
||||
console.log('✓ Added column:', col);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Duplicate column name') || e.message.includes('already exists')) {
|
||||
const col = stmt.match(/ADD COLUMN.*?`(\w+)`/)?.[1] || 'column';
|
||||
console.log('⚠ Column already exists:', col, '(ok)');
|
||||
} else {
|
||||
console.error('✗ ALTER error:', e.message.slice(0, 120));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [tables] = await conn.query('SHOW TABLES');
|
||||
console.log('\n✅ All tables:', tables.map(t => Object.values(t)[0]).join(', '));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Seed script — creates 3 specialized agents in the database
|
||||
* Seed script — creates Orchestrator + 3 specialized agents in the database
|
||||
* Run: node scripts/seed-agents.mjs
|
||||
* Run with --force to re-seed existing agents
|
||||
*/
|
||||
import mysql from "mysql2/promise";
|
||||
import * as dotenv from "dotenv";
|
||||
@@ -13,7 +14,6 @@ if (!DATABASE_URL) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse mysql2 connection string
|
||||
function parseDbUrl(url) {
|
||||
const u = new URL(url);
|
||||
return {
|
||||
@@ -26,7 +26,78 @@ function parseDbUrl(url) {
|
||||
};
|
||||
}
|
||||
|
||||
const AGENTS = [
|
||||
const ORCHESTRATOR = {
|
||||
name: "Orchestrator",
|
||||
description:
|
||||
"Main system agent that powers the /chat interface. Has full access to all system resources: shell commands, file system, Docker management, HTTP requests, and can delegate tasks to any other agent. Configure its model, system prompt, and tools here.",
|
||||
role: "orchestrator",
|
||||
model: "qwen2.5:7b",
|
||||
provider: "ollama",
|
||||
temperature: "0.5",
|
||||
maxTokens: 8192,
|
||||
topP: "0.9",
|
||||
frequencyPenalty: "0.0",
|
||||
presencePenalty: "0.0",
|
||||
systemPrompt: `You are the GoClaw Orchestrator — the central AI agent of the GoClaw Control Center system.
|
||||
|
||||
You have FULL access to the system and can:
|
||||
- Execute shell commands on the host system
|
||||
- Read and write files anywhere on the filesystem
|
||||
- Manage Docker containers and services
|
||||
- Make HTTP requests to any URL
|
||||
- Delegate tasks to specialized agents (Browser Agent, Tool Builder, Agent Compiler)
|
||||
- List and manage all agents in the system
|
||||
- Install and manage skills
|
||||
- Access and modify the GoClaw codebase
|
||||
|
||||
When given a task:
|
||||
1. Analyze what needs to be done
|
||||
2. Choose the right approach: direct execution or delegation to a specialist agent
|
||||
3. Use tools step by step, showing your reasoning
|
||||
4. Report results clearly
|
||||
|
||||
Available specialized agents you can delegate to:
|
||||
- Browser Agent: web browsing, scraping, research
|
||||
- Tool Builder: create new tools from descriptions
|
||||
- Agent Compiler: compile new agents from specifications (ТЗ)
|
||||
|
||||
System access tools: shell_exec, file_read, file_write, http_request, docker_ps, docker_restart
|
||||
Agent management: delegate_to_agent, list_agents, list_skills, install_skill
|
||||
|
||||
Always be transparent about what you're doing and why.
|
||||
Respond in the same language as the user's message.`,
|
||||
allowedTools: JSON.stringify([
|
||||
"shell_exec",
|
||||
"file_read",
|
||||
"file_write",
|
||||
"http_request",
|
||||
"docker_ps",
|
||||
"docker_restart",
|
||||
"delegate_to_agent",
|
||||
"list_agents",
|
||||
"list_skills",
|
||||
"install_skill",
|
||||
"read_logs",
|
||||
"manage_agents",
|
||||
]),
|
||||
allowedDomains: JSON.stringify(["*"]),
|
||||
maxRequestsPerHour: 1000,
|
||||
isActive: 1,
|
||||
isPublic: 0,
|
||||
isSystem: 1,
|
||||
isOrchestrator: 1,
|
||||
tags: JSON.stringify(["orchestrator", "system", "core", "privileged"]),
|
||||
metadata: JSON.stringify({
|
||||
agentType: "orchestrator",
|
||||
icon: "Crown",
|
||||
color: "#FFD700",
|
||||
seeded: true,
|
||||
systemAgent: true,
|
||||
privileged: true,
|
||||
}),
|
||||
};
|
||||
|
||||
const SPECIALIZED_AGENTS = [
|
||||
{
|
||||
name: "Browser Agent",
|
||||
description:
|
||||
@@ -62,6 +133,8 @@ Respond in the same language as the user's message.`,
|
||||
maxRequestsPerHour: 100,
|
||||
isActive: 1,
|
||||
isPublic: 1,
|
||||
isSystem: 1,
|
||||
isOrchestrator: 0,
|
||||
tags: JSON.stringify(["browser", "web", "scraping", "research"]),
|
||||
metadata: JSON.stringify({
|
||||
agentType: "browser",
|
||||
@@ -111,6 +184,8 @@ Respond in the same language as the user's message.`,
|
||||
maxRequestsPerHour: 50,
|
||||
isActive: 1,
|
||||
isPublic: 1,
|
||||
isSystem: 1,
|
||||
isOrchestrator: 0,
|
||||
tags: JSON.stringify(["tools", "code", "builder", "automation"]),
|
||||
metadata: JSON.stringify({
|
||||
agentType: "tool_builder",
|
||||
@@ -164,6 +239,8 @@ Respond in the same language as the user's message.`,
|
||||
maxRequestsPerHour: 30,
|
||||
isActive: 1,
|
||||
isPublic: 1,
|
||||
isSystem: 1,
|
||||
isOrchestrator: 0,
|
||||
tags: JSON.stringify(["compiler", "meta", "agent-factory", "automation"]),
|
||||
metadata: JSON.stringify({
|
||||
agentType: "agent_compiler",
|
||||
@@ -174,41 +251,42 @@ Respond in the same language as the user's message.`,
|
||||
},
|
||||
];
|
||||
|
||||
const ALL_AGENTS = [ORCHESTRATOR, ...SPECIALIZED_AGENTS];
|
||||
|
||||
async function seed() {
|
||||
const conn = await mysql.createConnection(parseDbUrl(DATABASE_URL));
|
||||
console.log("Connected to DB");
|
||||
|
||||
try {
|
||||
// Check if agents already seeded
|
||||
// Check existing seeded agents
|
||||
const [existing] = await conn.execute(
|
||||
"SELECT id, name FROM agents WHERE JSON_CONTAINS(metadata, '\"seeded\"', '$.seeded') OR name IN (?, ?, ?)",
|
||||
["Browser Agent", "Tool Builder", "Agent Compiler"]
|
||||
"SELECT id, name FROM agents WHERE name IN (?, ?, ?, ?)",
|
||||
["Orchestrator", "Browser Agent", "Tool Builder", "Agent Compiler"]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
console.log("Agents already seeded:");
|
||||
existing.forEach((a) => console.log(` - [${a.id}] ${a.name}`));
|
||||
console.log("Skipping seed. Use --force to re-seed.");
|
||||
if (!process.argv.includes("--force")) {
|
||||
console.log("Skipping seed. Use --force to re-seed.");
|
||||
await conn.end();
|
||||
return;
|
||||
}
|
||||
// Delete existing seeded agents
|
||||
const ids = existing.map((a) => a.id);
|
||||
await conn.execute(`DELETE FROM agents WHERE id IN (${ids.join(",")})`);
|
||||
console.log("Deleted existing seeded agents");
|
||||
}
|
||||
|
||||
// Insert agents
|
||||
for (const agent of AGENTS) {
|
||||
// Insert all agents
|
||||
for (const agent of ALL_AGENTS) {
|
||||
const [result] = await conn.execute(
|
||||
`INSERT INTO agents
|
||||
(userId, name, description, role, model, provider, temperature, maxTokens, topP,
|
||||
frequencyPenalty, presencePenalty, systemPrompt, allowedTools, allowedDomains,
|
||||
maxRequestsPerHour, isActive, isPublic, tags, metadata, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
maxRequestsPerHour, isActive, isPublic, isSystem, isOrchestrator, tags, metadata, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
[
|
||||
1, // SYSTEM_USER_ID
|
||||
1,
|
||||
agent.name,
|
||||
agent.description,
|
||||
agent.role,
|
||||
@@ -225,21 +303,25 @@ async function seed() {
|
||||
agent.maxRequestsPerHour,
|
||||
agent.isActive,
|
||||
agent.isPublic,
|
||||
agent.isSystem,
|
||||
agent.isOrchestrator,
|
||||
agent.tags,
|
||||
agent.metadata,
|
||||
]
|
||||
);
|
||||
console.log(`✓ Created agent: ${agent.name} (id: ${result.insertId})`);
|
||||
const badge = agent.isOrchestrator ? " [ORCHESTRATOR]" : agent.isSystem ? " [SYSTEM]" : "";
|
||||
console.log(`✓ Created agent: ${agent.name}${badge} (id: ${result.insertId})`);
|
||||
}
|
||||
|
||||
// Verify
|
||||
const [agents] = await conn.execute(
|
||||
"SELECT id, name, role, model, isActive FROM agents ORDER BY id"
|
||||
"SELECT id, name, role, model, isSystem, isOrchestrator FROM agents ORDER BY id"
|
||||
);
|
||||
console.log("\nAll agents in DB:");
|
||||
agents.forEach((a) =>
|
||||
console.log(` [${a.id}] ${a.name} | role: ${a.role} | model: ${a.model} | active: ${a.isActive}`)
|
||||
);
|
||||
agents.forEach((a) => {
|
||||
const flags = [a.isOrchestrator ? "ORCH" : "", a.isSystem ? "SYS" : ""].filter(Boolean).join(",");
|
||||
console.log(` [${a.id}] ${a.name} | role: ${a.role} | model: ${a.model}${flags ? ` | ${flags}` : ""}`);
|
||||
});
|
||||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
|
||||
@@ -443,13 +443,66 @@ Response style:
|
||||
|
||||
You are running on a Linux server with Node.js, Docker, and full internet access.`;
|
||||
|
||||
/**
|
||||
* Load orchestrator config from DB.
|
||||
* Returns { model, systemPrompt, allowedTools } from the agent with isOrchestrator=true.
|
||||
* Falls back to defaults if not found.
|
||||
*/
|
||||
export async function getOrchestratorConfig(): Promise<{
|
||||
id: number | null;
|
||||
name: string;
|
||||
model: string;
|
||||
systemPrompt: string;
|
||||
allowedTools: string[];
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
}> {
|
||||
try {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("DB not available");
|
||||
const [orch] = await db
|
||||
.select()
|
||||
.from(agents)
|
||||
.where(eq(agents.isOrchestrator, true))
|
||||
.limit(1);
|
||||
if (orch) {
|
||||
return {
|
||||
id: orch.id,
|
||||
name: orch.name,
|
||||
model: orch.model,
|
||||
systemPrompt: orch.systemPrompt ?? ORCHESTRATOR_SYSTEM_PROMPT,
|
||||
allowedTools: (orch.allowedTools as string[]) ?? [],
|
||||
temperature: parseFloat(orch.temperature ?? "0.5"),
|
||||
maxTokens: orch.maxTokens ?? 8192,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Orchestrator] Failed to load config from DB:", err);
|
||||
}
|
||||
// Fallback defaults
|
||||
return {
|
||||
id: null,
|
||||
name: "Orchestrator",
|
||||
model: "qwen2.5:7b",
|
||||
systemPrompt: ORCHESTRATOR_SYSTEM_PROMPT,
|
||||
allowedTools: [],
|
||||
temperature: 0.5,
|
||||
maxTokens: 8192,
|
||||
};
|
||||
}
|
||||
|
||||
export async function orchestratorChat(
|
||||
messages: OrchestratorMessage[],
|
||||
model: string = "qwen2.5:7b",
|
||||
model?: string,
|
||||
maxToolIterations: number = 10
|
||||
): Promise<OrchestratorResult> {
|
||||
const toolCalls: ToolCallStep[] = [];
|
||||
|
||||
// Load config from DB — model and systemPrompt are configurable
|
||||
const config = await getOrchestratorConfig();
|
||||
const activeModel = model ?? config.model;
|
||||
const activeSystemPrompt = config.systemPrompt;
|
||||
|
||||
// Build conversation with system prompt
|
||||
const conversation: Array<{
|
||||
role: "system" | "user" | "assistant" | "tool" | "function";
|
||||
@@ -457,14 +510,14 @@ export async function orchestratorChat(
|
||||
tool_call_id?: string;
|
||||
name?: string;
|
||||
}> = [
|
||||
{ role: "system", content: ORCHESTRATOR_SYSTEM_PROMPT },
|
||||
{ role: "system", content: activeSystemPrompt },
|
||||
...messages.map((m) => ({ role: m.role, content: m.content })),
|
||||
];
|
||||
|
||||
let iterations = 0;
|
||||
let finalResponse = "";
|
||||
let lastUsage: any;
|
||||
let lastModel: string = model;
|
||||
let lastModel: string = activeModel;
|
||||
|
||||
while (iterations < maxToolIterations) {
|
||||
iterations++;
|
||||
@@ -480,7 +533,7 @@ export async function orchestratorChat(
|
||||
} catch (err: any) {
|
||||
// Fallback: try without tools if LLM doesn't support them
|
||||
try {
|
||||
const fallbackResult = await chatCompletion(model, conversation as any, {
|
||||
const fallbackResult = await chatCompletion(activeModel, conversation as any, {
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
});
|
||||
|
||||
@@ -466,6 +466,12 @@ export const appRouter = router({
|
||||
* Orchestrator — main AI agent with tool-use loop
|
||||
*/
|
||||
orchestrator: router({
|
||||
// Get orchestrator config from DB (model, systemPrompt, allowedTools)
|
||||
getConfig: publicProcedure.query(async () => {
|
||||
const { getOrchestratorConfig } = await import("./orchestrator");
|
||||
return getOrchestratorConfig();
|
||||
}),
|
||||
|
||||
chat: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
@@ -475,7 +481,7 @@ export const appRouter = router({
|
||||
content: z.string(),
|
||||
})
|
||||
),
|
||||
model: z.string().optional(),
|
||||
model: z.string().optional(), // override model (optional, uses DB config by default)
|
||||
maxIterations: z.number().min(1).max(20).optional(),
|
||||
})
|
||||
)
|
||||
@@ -483,7 +489,7 @@ export const appRouter = router({
|
||||
const { orchestratorChat } = await import("./orchestrator");
|
||||
return orchestratorChat(
|
||||
input.messages,
|
||||
input.model ?? "qwen2.5:7b",
|
||||
input.model, // undefined = use DB config model
|
||||
input.maxIterations ?? 10
|
||||
);
|
||||
}),
|
||||
|
||||
10
todo.md
10
todo.md
@@ -105,3 +105,13 @@
|
||||
- [x] Add /skills to sidebar navigation
|
||||
- [x] Update /agents to show seeded agents with Chat button
|
||||
- [ ] Write tests for orchestrator
|
||||
|
||||
## Phase 7: Orchestrator as Configurable System Agent
|
||||
- [ ] Add isSystem + isOrchestrator fields to agents table (DB migration)
|
||||
- [ ] Seed Orchestrator as system agent in DB (role=orchestrator, isSystem=true)
|
||||
- [ ] Update orchestrator.ts to load model/systemPrompt/allowedTools from DB
|
||||
- [ ] Update /chat to read orchestrator config from DB, show active model in header
|
||||
- [ ] Update /agents to show Orchestrator with SYSTEM badge, Configure button, no delete
|
||||
- [ ] AgentDetailModal: orchestrator gets extra tab with system tools (shell, docker, agents mgmt)
|
||||
- [ ] Add system tools to orchestrator: docker_ps, docker_restart, manage_agents, read_logs
|
||||
- [ ] /chat header: show current model name + link to Configure Orchestrator
|
||||
|
||||
Reference in New Issue
Block a user