Checkpoint: Phase 7 complete: Orchestrator Agent добавлен в /agents с меткой CROWN/SYSTEM, кнопками Configure и Open Chat. /chat читает конфиг оркестратора из БД (модель, промпт, инструменты). AgentDetailModal поддерживает isOrchestrator. 24 теста пройдены.

This commit is contained in:
Manus
2026-03-20 17:48:21 -04:00
parent c2fdfdbf72
commit 7aa8eee2ca
11 changed files with 1339 additions and 128 deletions

View File

@@ -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(', '));

View File

@@ -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();
}