v1.6.0: Replace Rig with Agents Council + Keep FULL RAG
This commit is contained in:
310
src/tools/agents-council.ts
Normal file
310
src/tools/agents-council.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* Agents Council Integration for QwenClaw
|
||||
*
|
||||
* Replaces Rig with Agents Council for multi-agent orchestration
|
||||
* while maintaining FULL RAG capabilities
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import { join } from "path";
|
||||
import { existsSync, readFileSync, writeFile } from "fs";
|
||||
|
||||
const COUNCIL_DIR = join(process.env.HOME || process.env.USERPROFILE || "", ".agents-council");
|
||||
const QWENCLAW_DIR = process.env.QWENCLAW_DIR || join(process.env.HOME || process.env.USERPROFILE || "", "qwenclaw");
|
||||
const STATE_FILE = join(COUNCIL_DIR, "state.json");
|
||||
|
||||
export interface CouncilConfig {
|
||||
autoStart: boolean;
|
||||
desktopApp: boolean;
|
||||
agents: Record<string, AgentConfig>;
|
||||
rag: RAGConfig;
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
enabled: boolean;
|
||||
model: string;
|
||||
autoSummon: boolean;
|
||||
}
|
||||
|
||||
export interface RAGConfig {
|
||||
enabled: boolean;
|
||||
vectorStore: 'sqlite' | 'chroma' | 'pinecone';
|
||||
shareAcrossAgents: boolean;
|
||||
topK: number;
|
||||
threshold: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: CouncilConfig = {
|
||||
autoStart: true,
|
||||
desktopApp: true,
|
||||
agents: {
|
||||
qwen: { enabled: true, model: 'qwen-plus', autoSummon: true },
|
||||
claude: { enabled: true, model: 'claude-sonnet-4-5-20250929', autoSummon: false },
|
||||
codex: { enabled: true, model: 'codex-latest', autoSummon: false },
|
||||
},
|
||||
rag: {
|
||||
enabled: true,
|
||||
vectorStore: 'sqlite',
|
||||
shareAcrossAgents: true,
|
||||
topK: 5,
|
||||
threshold: 0.7,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Agents Council Client
|
||||
*/
|
||||
export class AgentsCouncilClient {
|
||||
private config: CouncilConfig;
|
||||
private running: boolean = false;
|
||||
|
||||
constructor(config: Partial<CouncilConfig> = {}) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Agents Council MCP server
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
if (this.running) {
|
||||
console.log("[Council] Already running");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[Council] Starting Agents Council...");
|
||||
|
||||
const councilProcess = spawn("npx", ["agents-council@latest", "mcp"], {
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
detached: false,
|
||||
});
|
||||
|
||||
councilProcess.stdout?.on("data", (data) => {
|
||||
console.log(`[Council] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
councilProcess.stderr?.on("data", (data) => {
|
||||
console.error(`[Council Error] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
this.running = true;
|
||||
|
||||
// Wait for council to start
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
console.log("[Council] ✅ Started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop council
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
this.running = false;
|
||||
console.log("[Council] Stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Summon agent to council
|
||||
*/
|
||||
async summon(agent: string): Promise<void> {
|
||||
console.log(`[Council] Summoning ${agent}...`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("npx", ["agents-council@latest", "summon", agent], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
console.log(`[Council] ✅ ${agent} summoned`);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Failed to summon ${agent}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start multi-agent discussion
|
||||
*/
|
||||
async discuss(options: DiscussionOptions): Promise<DiscussionResult> {
|
||||
console.log(`[Council] Starting discussion: ${options.topic}`);
|
||||
|
||||
const args = [
|
||||
"agents-council@latest",
|
||||
"discuss",
|
||||
"--topic", options.topic,
|
||||
"--agents", options.agents.join(","),
|
||||
];
|
||||
|
||||
if (options.context) {
|
||||
args.push("--context", options.context);
|
||||
}
|
||||
|
||||
if (options.ragContext) {
|
||||
args.push("--rag");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("npx", args, {
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let output = "";
|
||||
proc.stdout?.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve({
|
||||
summary: output,
|
||||
agents: options.agents,
|
||||
topic: options.topic,
|
||||
});
|
||||
} else {
|
||||
reject(new Error("Discussion failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get council status
|
||||
*/
|
||||
async getStatus(): Promise<CouncilStatus> {
|
||||
if (!existsSync(STATE_FILE)) {
|
||||
return { running: false, agents: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf-8"));
|
||||
return {
|
||||
running: this.running,
|
||||
agents: state.agents || [],
|
||||
discussions: state.discussions || 0,
|
||||
};
|
||||
} catch {
|
||||
return { running: false, agents: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Council Hall desktop app
|
||||
*/
|
||||
async monitor(): Promise<void> {
|
||||
console.log("[Council] Opening Council Hall...");
|
||||
|
||||
spawn("npx", ["agents-council@latest", "desktop"], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface DiscussionOptions {
|
||||
topic: string;
|
||||
context?: string;
|
||||
agents: string[];
|
||||
roles?: Record<string, string>;
|
||||
ragContext?: boolean | RAGOptions;
|
||||
output?: 'markdown' | 'json';
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
interface RAGOptions {
|
||||
searchQuery?: string;
|
||||
documents?: string[];
|
||||
topK?: number;
|
||||
includeInContext?: boolean;
|
||||
}
|
||||
|
||||
interface DiscussionResult {
|
||||
summary: string;
|
||||
agents: string[];
|
||||
topic: string;
|
||||
ragDocuments?: any[];
|
||||
}
|
||||
|
||||
interface CouncilStatus {
|
||||
running: boolean;
|
||||
agents: string[];
|
||||
discussions?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Agents Council with RAG
|
||||
*/
|
||||
export async function initAgentsCouncil(): Promise<AgentsCouncilClient> {
|
||||
const client = new AgentsCouncilClient();
|
||||
|
||||
// Auto-start if configured
|
||||
if (client['config'].autoStart) {
|
||||
await client.start();
|
||||
}
|
||||
|
||||
// Summon auto-summon agents
|
||||
for (const [agent, config] of Object.entries(client['config'].agents)) {
|
||||
if (config.autoSummon) {
|
||||
await client.summon(agent);
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI command for council
|
||||
*/
|
||||
export async function councilCommand(args: string[]): Promise<void> {
|
||||
console.log("🏛️ Agents Council for QwenClaw\n");
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log("Usage: qwenclaw council <command> [options]");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
console.log(" start - Start council MCP server");
|
||||
console.log(" status - Check council status");
|
||||
console.log(" summon <agent> - Summon agent (claude/codex/qwen)");
|
||||
console.log(" discuss - Start multi-agent discussion");
|
||||
console.log(" monitor - Open Council Hall desktop");
|
||||
console.log(" config - Edit configuration");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" qwenclaw council start");
|
||||
console.log(" qwenclaw council summon claude");
|
||||
console.log(" qwenclaw council discuss --topic 'Code Review'");
|
||||
return;
|
||||
}
|
||||
|
||||
const command = args[0];
|
||||
const client = new AgentsCouncilClient();
|
||||
|
||||
switch (command) {
|
||||
case "start":
|
||||
await client.start();
|
||||
break;
|
||||
|
||||
case "status":
|
||||
const status = await client.getStatus();
|
||||
console.log("Council Status:");
|
||||
console.log(` Running: ${status.running ? '✅' : '❌'}`);
|
||||
console.log(` Agents: ${status.agents.join(", ") || 'none'}`);
|
||||
console.log(` Discussions: ${status.discussions || 0}`);
|
||||
break;
|
||||
|
||||
case "summon":
|
||||
if (!args[1]) {
|
||||
console.log("[ERROR] Agent name required");
|
||||
return;
|
||||
}
|
||||
await client.summon(args[1]);
|
||||
break;
|
||||
|
||||
case "monitor":
|
||||
await client.monitor();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`[ERROR] Unknown command: ${command}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user