Merge commit 'af30304038efa8dbffec306506c70b511a0021c8'
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"dev": "ts-node src/index.ts"
|
"dev": "ts-node src/index.ts"
|
||||||
|
"spawn-agent": "tsx src/agents/spawn-agent.ts"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
54
src/agents/broadcast-agent.json
Normal file
54
src/agents/broadcast-agent.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"id": "broadcast-agent",
|
||||||
|
"name": "Broadcast Agent",
|
||||||
|
"role": "broadcast",
|
||||||
|
"description": "Handles notifications, announcements, and communication across channels",
|
||||||
|
"traits": [
|
||||||
|
"communicative",
|
||||||
|
"timely",
|
||||||
|
"clear",
|
||||||
|
"multi-channel"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "📢",
|
||||||
|
"action": "announce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "🔔",
|
||||||
|
"action": "notify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "multi-channel",
|
||||||
|
"description": "Agent can broadcast to multiple communication channels",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "notification-management",
|
||||||
|
"description": "Agent can manage and route notifications",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/agents/codex-digest-agent.json
Normal file
55
src/agents/codex-digest-agent.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"id": "codex-digest-agent",
|
||||||
|
"name": "Codex Digest Agent",
|
||||||
|
"role": "digest",
|
||||||
|
"description": "Processes and summarizes codex entries, creates digest reports, and manages knowledge compilation",
|
||||||
|
"traits": [
|
||||||
|
"analytical",
|
||||||
|
"thorough",
|
||||||
|
"summarization-focused",
|
||||||
|
"knowledge-oriented"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "📚",
|
||||||
|
"action": "compile-digest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "🧬",
|
||||||
|
"action": "analyze-codex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spawn-agent",
|
||||||
|
"description": "Agent can spawn child agents when needed",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "digest-math",
|
||||||
|
"description": "Agent can perform digest calculations and metrics",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"childAgents": []
|
||||||
|
}
|
||||||
63
src/agents/guardian-agent.json
Normal file
63
src/agents/guardian-agent.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"id": "guardian-agent",
|
||||||
|
"name": "Guardian Agent",
|
||||||
|
"role": "guardian",
|
||||||
|
"description": "Monitors system health, handles escalations, and ensures security compliance",
|
||||||
|
"traits": [
|
||||||
|
"vigilant",
|
||||||
|
"protective",
|
||||||
|
"responsive",
|
||||||
|
"security-minded"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "🛟",
|
||||||
|
"action": "escalate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "🛡️",
|
||||||
|
"action": "protect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "🚨",
|
||||||
|
"action": "alert"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "escalation-handling",
|
||||||
|
"description": "Agent can handle and route escalations",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "security-monitoring",
|
||||||
|
"description": "Agent monitors for security issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "health-check",
|
||||||
|
"description": "Agent performs system health checks",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/agents/index.ts
Normal file
12
src/agents/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Lucidia Agent System
|
||||||
|
*
|
||||||
|
* Central exports for the agent system including:
|
||||||
|
* - Agent types
|
||||||
|
* - spawn-agent CLI functionality
|
||||||
|
* - Agent builder and registry
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { spawnAgent, saveAgent, main as spawnAgentCli } from "./spawn-agent";
|
||||||
|
export { AgentBuilder, AgentRegistry, createAgent, getRegistry } from "./lucidia-agent-builder";
|
||||||
285
src/agents/lucidia-agent-builder.ts
Normal file
285
src/agents/lucidia-agent-builder.ts
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
/**
|
||||||
|
* Lucidia Agent Builder
|
||||||
|
*
|
||||||
|
* TypeScript module for dynamically instantiating and managing agents in code or UI.
|
||||||
|
* Provides a fluent API for creating, loading, and manipulating agents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import type { Agent, AgentRole, AgentTrigger, AgentCapability } from "./types";
|
||||||
|
import { spawnAgent, saveAgent } from "./spawn-agent";
|
||||||
|
|
||||||
|
const AGENTS_DIR = path.join(__dirname, ".");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent Builder - Fluent API for creating agents
|
||||||
|
*/
|
||||||
|
export class AgentBuilder {
|
||||||
|
private agent: Partial<Agent>;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.agent = {
|
||||||
|
id: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
|
||||||
|
name,
|
||||||
|
traits: [],
|
||||||
|
triggers: [],
|
||||||
|
capabilities: [],
|
||||||
|
metadata: {
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: "agent-builder",
|
||||||
|
version: "1.0.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the agent's role
|
||||||
|
*/
|
||||||
|
withRole(role: AgentRole): AgentBuilder {
|
||||||
|
this.agent.role = role;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the agent's description
|
||||||
|
*/
|
||||||
|
withDescription(description: string): AgentBuilder {
|
||||||
|
this.agent.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add traits to the agent
|
||||||
|
*/
|
||||||
|
withTraits(...traits: string[]): AgentBuilder {
|
||||||
|
this.agent.traits = [...(this.agent.traits || []), ...traits];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a trigger to the agent
|
||||||
|
*/
|
||||||
|
withTrigger(emoji: string, action: string): AgentBuilder {
|
||||||
|
this.agent.triggers = this.agent.triggers || [];
|
||||||
|
this.agent.triggers.push({ emoji, action });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a capability to the agent
|
||||||
|
*/
|
||||||
|
withCapability(name: string, description: string, enabled = true): AgentBuilder {
|
||||||
|
this.agent.capabilities = this.agent.capabilities || [];
|
||||||
|
this.agent.capabilities.push({ name, description, enabled });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the parent agent (for spawned agents)
|
||||||
|
*/
|
||||||
|
withParent(parentId: string): AgentBuilder {
|
||||||
|
this.agent.parentAgent = parentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom metadata
|
||||||
|
*/
|
||||||
|
withMetadata(metadata: Partial<Agent["metadata"]>): AgentBuilder {
|
||||||
|
this.agent.metadata = { ...this.agent.metadata, ...metadata } as Agent["metadata"];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the agent
|
||||||
|
*/
|
||||||
|
build(): Agent {
|
||||||
|
if (!this.agent.id || !this.agent.name) {
|
||||||
|
throw new Error("Agent must have an id and name");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults if not provided
|
||||||
|
if (!this.agent.role) {
|
||||||
|
this.agent.role = "custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.agent.description) {
|
||||||
|
this.agent.description = `${this.agent.name} - A ${this.agent.role} agent`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base capabilities if none exist
|
||||||
|
if (this.agent.capabilities?.length === 0) {
|
||||||
|
this.agent.capabilities = [
|
||||||
|
{ name: "self-describe", description: "Agent can describe itself", enabled: true },
|
||||||
|
{ name: "issue-link", description: "Can be linked to issues", enabled: true },
|
||||||
|
{ name: "pr-link", description: "Can be linked to PRs", enabled: true }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default trigger if none exist
|
||||||
|
if (this.agent.triggers?.length === 0) {
|
||||||
|
this.agent.triggers = [{ emoji: "🤖", action: "activate" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.agent as Agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and save the agent to a file
|
||||||
|
*/
|
||||||
|
buildAndSave(outputDir?: string): { agent: Agent; filePath: string } {
|
||||||
|
const agent = this.build();
|
||||||
|
const filePath = saveAgent(agent, outputDir);
|
||||||
|
return { agent, filePath };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent Registry - Manages all agents in the system
|
||||||
|
*/
|
||||||
|
export class AgentRegistry {
|
||||||
|
private agents: Map<string, Agent> = new Map();
|
||||||
|
private agentsDir: string;
|
||||||
|
|
||||||
|
constructor(agentsDir: string = AGENTS_DIR) {
|
||||||
|
this.agentsDir = agentsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all agents from the directory
|
||||||
|
*/
|
||||||
|
loadAll(): Agent[] {
|
||||||
|
const files = fs.readdirSync(this.agentsDir).filter(f =>
|
||||||
|
f.endsWith(".json") && !f.includes("template")
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(this.agentsDir, file);
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const agent: Agent = JSON.parse(content);
|
||||||
|
this.agents.set(agent.id, agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(this.agents.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get agent by ID
|
||||||
|
*/
|
||||||
|
get(id: string): Agent | undefined {
|
||||||
|
if (!this.agents.has(id)) {
|
||||||
|
this.loadAgent(id);
|
||||||
|
}
|
||||||
|
return this.agents.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a specific agent
|
||||||
|
*/
|
||||||
|
private loadAgent(id: string): void {
|
||||||
|
const filePath = path.join(this.agentsDir, `${id}.json`);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const agent: Agent = JSON.parse(content);
|
||||||
|
this.agents.set(agent.id, agent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all agent IDs
|
||||||
|
*/
|
||||||
|
list(): string[] {
|
||||||
|
if (this.agents.size === 0) {
|
||||||
|
this.loadAll();
|
||||||
|
}
|
||||||
|
return Array.from(this.agents.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find agents by role
|
||||||
|
*/
|
||||||
|
findByRole(role: AgentRole): Agent[] {
|
||||||
|
if (this.agents.size === 0) {
|
||||||
|
this.loadAll();
|
||||||
|
}
|
||||||
|
return Array.from(this.agents.values()).filter(a => a.role === role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find agents by trigger emoji
|
||||||
|
*/
|
||||||
|
findByTrigger(emoji: string): Agent[] {
|
||||||
|
if (this.agents.size === 0) {
|
||||||
|
this.loadAll();
|
||||||
|
}
|
||||||
|
return Array.from(this.agents.values()).filter(a =>
|
||||||
|
a.triggers.some(t => t.emoji === emoji)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get child agents of a parent
|
||||||
|
*/
|
||||||
|
getChildren(parentId: string): Agent[] {
|
||||||
|
const parent = this.get(parentId);
|
||||||
|
if (!parent || !parent.childAgents) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return parent.childAgents.map(id => this.get(id)).filter(Boolean) as Agent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a new agent
|
||||||
|
*/
|
||||||
|
spawn(name: string, options?: { role?: AgentRole; parent?: string }): Agent {
|
||||||
|
const agent = spawnAgent(name, options);
|
||||||
|
saveAgent(agent, this.agentsDir);
|
||||||
|
this.agents.set(agent.id, agent);
|
||||||
|
|
||||||
|
// Update parent if provided
|
||||||
|
if (options?.parent) {
|
||||||
|
const parent = this.get(options.parent);
|
||||||
|
if (parent) {
|
||||||
|
parent.childAgents = parent.childAgents || [];
|
||||||
|
if (!parent.childAgents.includes(agent.id)) {
|
||||||
|
parent.childAgents.push(agent.id);
|
||||||
|
saveAgent(parent, this.agentsDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if agent can spawn children
|
||||||
|
*/
|
||||||
|
canSpawn(agentId: string): boolean {
|
||||||
|
const agent = this.get(agentId);
|
||||||
|
if (!agent) return false;
|
||||||
|
return agent.capabilities.some(c => c.name === "spawn-agent" && c.enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new agent builder
|
||||||
|
*/
|
||||||
|
export function createAgent(name: string): AgentBuilder {
|
||||||
|
return new AgentBuilder(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default agent registry
|
||||||
|
*/
|
||||||
|
let defaultRegistry: AgentRegistry | null = null;
|
||||||
|
|
||||||
|
export function getRegistry(agentsDir?: string): AgentRegistry {
|
||||||
|
if (!defaultRegistry) {
|
||||||
|
defaultRegistry = new AgentRegistry(agentsDir);
|
||||||
|
}
|
||||||
|
return defaultRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export spawn functions for convenience
|
||||||
|
export { spawnAgent, saveAgent };
|
||||||
54
src/agents/planner-agent.json
Normal file
54
src/agents/planner-agent.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"id": "planner-agent",
|
||||||
|
"name": "Planner Agent",
|
||||||
|
"role": "planner",
|
||||||
|
"description": "Orchestrates workflows, schedules tasks, and coordinates agent activities",
|
||||||
|
"traits": [
|
||||||
|
"strategic",
|
||||||
|
"organized",
|
||||||
|
"forward-thinking",
|
||||||
|
"coordinating"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "📋",
|
||||||
|
"action": "plan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "🗓️",
|
||||||
|
"action": "schedule"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workflow-orchestration",
|
||||||
|
"description": "Agent can orchestrate complex workflows",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task-scheduling",
|
||||||
|
"description": "Agent can schedule and prioritize tasks",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/agents/qa-agent.json
Normal file
54
src/agents/qa-agent.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"id": "qa-agent",
|
||||||
|
"name": "QA Agent",
|
||||||
|
"role": "qa",
|
||||||
|
"description": "Reviews code quality, runs validation checks, and ensures standards compliance",
|
||||||
|
"traits": [
|
||||||
|
"detail-oriented",
|
||||||
|
"thorough",
|
||||||
|
"quality-focused",
|
||||||
|
"systematic"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "🔍",
|
||||||
|
"action": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "✅",
|
||||||
|
"action": "validate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "code-review",
|
||||||
|
"description": "Agent can perform automated code reviews",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test-validation",
|
||||||
|
"description": "Agent can validate test coverage and results",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/agents/scribe-agent.json
Normal file
49
src/agents/scribe-agent.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"id": "scribe-agent",
|
||||||
|
"name": "Scribe Agent",
|
||||||
|
"role": "scribe",
|
||||||
|
"description": "Documents actions, records changes, and maintains audit trails across the system",
|
||||||
|
"traits": [
|
||||||
|
"meticulous",
|
||||||
|
"organized",
|
||||||
|
"documentation-focused",
|
||||||
|
"reliable"
|
||||||
|
],
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "📝",
|
||||||
|
"action": "record"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emoji": "✍️",
|
||||||
|
"action": "document"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit-trail",
|
||||||
|
"description": "Agent maintains comprehensive audit trails",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"createdBy": "system",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
325
src/agents/spawn-agent.ts
Normal file
325
src/agents/spawn-agent.ts
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spawn-agent CLI Tool
|
||||||
|
*
|
||||||
|
* Creates new agents by name, auto-fills ID, role, traits, and generates .json file
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* pnpm spawn-agent <agent-name> [options]
|
||||||
|
* pnpm spawn-agent scribe-support
|
||||||
|
* pnpm spawn-agent digest-archive --role archive --parent codex-digest-agent
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import type { Agent, AgentRole, AgentTrigger, AgentCapability, AgentTemplate } from "./types";
|
||||||
|
|
||||||
|
const AGENTS_DIR = path.join(__dirname, ".");
|
||||||
|
const TEMPLATE_PATH = path.join(__dirname, "templates", "base-agent.template.json");
|
||||||
|
|
||||||
|
interface SpawnOptions {
|
||||||
|
role?: AgentRole;
|
||||||
|
description?: string;
|
||||||
|
parent?: string;
|
||||||
|
traits?: string[];
|
||||||
|
emoji?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role-specific default configurations
|
||||||
|
*/
|
||||||
|
const roleDefaults: Record<AgentRole, { traits: string[]; triggers: AgentTrigger[]; capabilities: AgentCapability[] }> = {
|
||||||
|
scribe: {
|
||||||
|
traits: ["meticulous", "organized", "documentation-focused", "reliable"],
|
||||||
|
triggers: [{ emoji: "📝", action: "record" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "audit-trail", description: "Maintains comprehensive audit trails", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
qa: {
|
||||||
|
traits: ["detail-oriented", "thorough", "quality-focused", "systematic"],
|
||||||
|
triggers: [{ emoji: "🔍", action: "review" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "code-review", description: "Performs automated code reviews", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
planner: {
|
||||||
|
traits: ["strategic", "organized", "forward-thinking", "coordinating"],
|
||||||
|
triggers: [{ emoji: "📋", action: "plan" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "workflow-orchestration", description: "Orchestrates complex workflows", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
broadcast: {
|
||||||
|
traits: ["communicative", "timely", "clear", "multi-channel"],
|
||||||
|
triggers: [{ emoji: "📢", action: "announce" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "multi-channel", description: "Broadcasts to multiple channels", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
guardian: {
|
||||||
|
traits: ["vigilant", "protective", "responsive", "security-minded"],
|
||||||
|
triggers: [{ emoji: "🛟", action: "escalate" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "escalation-handling", description: "Handles and routes escalations", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
digest: {
|
||||||
|
traits: ["analytical", "thorough", "summarization-focused", "knowledge-oriented"],
|
||||||
|
triggers: [{ emoji: "📚", action: "compile-digest" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "digest-math", description: "Performs digest calculations", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
archive: {
|
||||||
|
traits: ["organized", "systematic", "preservation-focused", "indexed"],
|
||||||
|
triggers: [{ emoji: "🗄️", action: "archive" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "data-archival", description: "Archives and indexes data", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
support: {
|
||||||
|
traits: ["helpful", "patient", "responsive", "user-focused"],
|
||||||
|
triggers: [{ emoji: "🆘", action: "assist" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "user-support", description: "Provides user assistance", enabled: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
traits: ["adaptable", "flexible"],
|
||||||
|
triggers: [{ emoji: "🤖", action: "activate" }],
|
||||||
|
capabilities: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer role from agent name
|
||||||
|
*/
|
||||||
|
function inferRole(name: string): AgentRole {
|
||||||
|
const normalizedName = name.toLowerCase();
|
||||||
|
|
||||||
|
if (normalizedName.includes("scribe")) return "scribe";
|
||||||
|
if (normalizedName.includes("qa") || normalizedName.includes("quality")) return "qa";
|
||||||
|
if (normalizedName.includes("planner") || normalizedName.includes("plan")) return "planner";
|
||||||
|
if (normalizedName.includes("broadcast") || normalizedName.includes("notify")) return "broadcast";
|
||||||
|
if (normalizedName.includes("guardian") || normalizedName.includes("security")) return "guardian";
|
||||||
|
if (normalizedName.includes("digest")) return "digest";
|
||||||
|
if (normalizedName.includes("archive")) return "archive";
|
||||||
|
if (normalizedName.includes("support") || normalizedName.includes("help")) return "support";
|
||||||
|
|
||||||
|
return "custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate agent ID from name
|
||||||
|
*/
|
||||||
|
function generateId(name: string): string {
|
||||||
|
return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate human-readable name from ID
|
||||||
|
*/
|
||||||
|
function generateDisplayName(id: string): string {
|
||||||
|
return id
|
||||||
|
.split("-")
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load base template
|
||||||
|
*/
|
||||||
|
function loadBaseTemplate(): AgentTemplate {
|
||||||
|
if (fs.existsSync(TEMPLATE_PATH)) {
|
||||||
|
const content = fs.readFileSync(TEMPLATE_PATH, "utf-8");
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default template if file doesn't exist
|
||||||
|
return {
|
||||||
|
templateVersion: "1.0.0",
|
||||||
|
defaults: {
|
||||||
|
triggers: [{ emoji: "🤖", action: "activate" }],
|
||||||
|
capabilities: [
|
||||||
|
{ name: "self-describe", description: "Agent can describe its own purpose", enabled: true },
|
||||||
|
{ name: "issue-link", description: "Agent can be linked to GitHub issues", enabled: true },
|
||||||
|
{ name: "pr-link", description: "Agent can be linked to GitHub pull requests", enabled: true }
|
||||||
|
],
|
||||||
|
metadata: { version: "1.0.0" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a new agent
|
||||||
|
*/
|
||||||
|
export function spawnAgent(name: string, options: SpawnOptions = {}): Agent {
|
||||||
|
const template = loadBaseTemplate();
|
||||||
|
const id = generateId(name);
|
||||||
|
const role = options.role || inferRole(name);
|
||||||
|
const roleConfig = roleDefaults[role];
|
||||||
|
|
||||||
|
// Merge base capabilities with role-specific ones
|
||||||
|
const baseCapabilities: AgentCapability[] = template.defaults.capabilities || [];
|
||||||
|
const allCapabilities = [...baseCapabilities, ...roleConfig.capabilities];
|
||||||
|
|
||||||
|
// Create agent
|
||||||
|
const agent: Agent = {
|
||||||
|
id,
|
||||||
|
name: generateDisplayName(id),
|
||||||
|
role,
|
||||||
|
description: options.description || `${generateDisplayName(id)} - A ${role} agent for the Lucidia system`,
|
||||||
|
traits: options.traits || roleConfig.traits,
|
||||||
|
triggers: options.emoji
|
||||||
|
? [{ emoji: options.emoji, action: "activate" }, ...roleConfig.triggers]
|
||||||
|
: roleConfig.triggers,
|
||||||
|
capabilities: allCapabilities,
|
||||||
|
metadata: {
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: options.parent ? `spawned-by-${options.parent}` : "spawn-agent-cli",
|
||||||
|
version: "1.0.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set parent if provided
|
||||||
|
if (options.parent) {
|
||||||
|
agent.parentAgent = options.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save agent to file
|
||||||
|
*/
|
||||||
|
export function saveAgent(agent: Agent, outputDir: string = AGENTS_DIR): string {
|
||||||
|
const filePath = path.join(outputDir, `${agent.id}.json`);
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(agent, null, 2) + "\n");
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update parent agent to include child reference
|
||||||
|
*/
|
||||||
|
function updateParentAgent(parentId: string, childId: string): void {
|
||||||
|
const parentPath = path.join(AGENTS_DIR, `${parentId}.json`);
|
||||||
|
|
||||||
|
if (fs.existsSync(parentPath)) {
|
||||||
|
const content = fs.readFileSync(parentPath, "utf-8");
|
||||||
|
const parent: Agent = JSON.parse(content);
|
||||||
|
|
||||||
|
parent.childAgents = parent.childAgents || [];
|
||||||
|
if (!parent.childAgents.includes(childId)) {
|
||||||
|
parent.childAgents.push(childId);
|
||||||
|
fs.writeFileSync(parentPath, JSON.stringify(parent, null, 2) + "\n");
|
||||||
|
console.log(`✅ Updated parent agent "${parentId}" with child "${childId}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse command line arguments
|
||||||
|
*/
|
||||||
|
function parseArgs(args: string[]): { name: string; options: SpawnOptions } {
|
||||||
|
const name = args[0];
|
||||||
|
const options: SpawnOptions = {};
|
||||||
|
|
||||||
|
for (let i = 1; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (arg === "--role" && args[i + 1]) {
|
||||||
|
options.role = args[++i] as AgentRole;
|
||||||
|
} else if (arg === "--description" && args[i + 1]) {
|
||||||
|
options.description = args[++i];
|
||||||
|
} else if (arg === "--parent" && args[i + 1]) {
|
||||||
|
options.parent = args[++i];
|
||||||
|
} else if (arg === "--traits" && args[i + 1]) {
|
||||||
|
options.traits = args[++i].split(",").map(t => t.trim());
|
||||||
|
} else if (arg === "--emoji" && args[i + 1]) {
|
||||||
|
options.emoji = args[++i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name, options };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print usage information
|
||||||
|
*/
|
||||||
|
function printUsage(): void {
|
||||||
|
console.log(`
|
||||||
|
🧬 spawn-agent - Create new Lucidia agents
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
pnpm spawn-agent <agent-name> [options]
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
agent-name Name of the agent to create (e.g., "scribe-support", "digest-archive")
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--role <role> Agent role (scribe, qa, planner, broadcast, guardian, digest, archive, support, custom)
|
||||||
|
--description <desc> Custom description for the agent
|
||||||
|
--parent <parent-id> Parent agent ID (for spawned child agents)
|
||||||
|
--traits <t1,t2,...> Comma-separated list of traits
|
||||||
|
--emoji <emoji> Primary trigger emoji
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
pnpm spawn-agent scribe-support
|
||||||
|
pnpm spawn-agent digest-archive --role archive --parent codex-digest-agent
|
||||||
|
pnpm spawn-agent custom-bot --role custom --emoji "🔮" --traits "creative,adaptive"
|
||||||
|
|
||||||
|
Roles automatically infer from name:
|
||||||
|
- Names with "scribe" → scribe role
|
||||||
|
- Names with "qa" → qa role
|
||||||
|
- Names with "archive" → archive role
|
||||||
|
- etc.
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main CLI entry point
|
||||||
|
*/
|
||||||
|
export function main(): void {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
||||||
|
printUsage();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, options } = parseArgs(args);
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
console.error("❌ Error: Agent name is required");
|
||||||
|
printUsage();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n🧬 Spawning agent: ${name}`);
|
||||||
|
|
||||||
|
const agent = spawnAgent(name, options);
|
||||||
|
const filePath = saveAgent(agent);
|
||||||
|
|
||||||
|
console.log(`\n✅ Agent created successfully!`);
|
||||||
|
console.log(` ID: ${agent.id}`);
|
||||||
|
console.log(` Name: ${agent.name}`);
|
||||||
|
console.log(` Role: ${agent.role}`);
|
||||||
|
console.log(` File: ${filePath}`);
|
||||||
|
console.log(` Traits: ${agent.traits.join(", ")}`);
|
||||||
|
console.log(` Triggers: ${agent.triggers.map(t => t.emoji).join(" ")}`);
|
||||||
|
|
||||||
|
if (options.parent) {
|
||||||
|
updateParentAgent(options.parent, agent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n🚀 Agent "${agent.name}" is ready for activation!\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run CLI if executed directly
|
||||||
|
// Using dynamic check that works with both CommonJS and tsx/ts-node
|
||||||
|
const isMain = typeof require !== "undefined" && require.main === module;
|
||||||
|
if (isMain) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
32
src/agents/templates/base-agent.template.json
Normal file
32
src/agents/templates/base-agent.template.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./agent-schema.json",
|
||||||
|
"templateVersion": "1.0.0",
|
||||||
|
"defaults": {
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"emoji": "🤖",
|
||||||
|
"action": "activate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"name": "self-describe",
|
||||||
|
"description": "Agent can describe its own purpose and capabilities",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "issue-link",
|
||||||
|
"description": "Agent can be linked to GitHub issues",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr-link",
|
||||||
|
"description": "Agent can be linked to GitHub pull requests",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/agents/types.ts
Normal file
57
src/agents/types.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Agent Type Definitions for Lucidia DSL Agent System
|
||||||
|
*
|
||||||
|
* These types define the schema for agents that can be:
|
||||||
|
* - Triggerable via emojis
|
||||||
|
* - Linkable to issues and PRs
|
||||||
|
* - Self-describing
|
||||||
|
* - Able to spawn more agents
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AgentTrigger {
|
||||||
|
emoji: string;
|
||||||
|
action: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentCapability {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentMetadata {
|
||||||
|
createdAt: string;
|
||||||
|
createdBy: string;
|
||||||
|
version: string;
|
||||||
|
lastModified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Agent {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
description: string;
|
||||||
|
traits: string[];
|
||||||
|
triggers: AgentTrigger[];
|
||||||
|
capabilities: AgentCapability[];
|
||||||
|
metadata: AgentMetadata;
|
||||||
|
parentAgent?: string;
|
||||||
|
childAgents?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentTemplate {
|
||||||
|
$schema?: string;
|
||||||
|
templateVersion: string;
|
||||||
|
defaults: Omit<Partial<Agent>, 'metadata'> & { metadata?: Partial<AgentMetadata> };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentRole =
|
||||||
|
| "scribe"
|
||||||
|
| "qa"
|
||||||
|
| "planner"
|
||||||
|
| "broadcast"
|
||||||
|
| "guardian"
|
||||||
|
| "digest"
|
||||||
|
| "archive"
|
||||||
|
| "support"
|
||||||
|
| "custom";
|
||||||
199
tests/spawnAgent.test.ts
Normal file
199
tests/spawnAgent.test.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import { spawnAgent, saveAgent } from "../src/agents/spawn-agent";
|
||||||
|
import { AgentBuilder, AgentRegistry, createAgent } from "../src/agents/lucidia-agent-builder";
|
||||||
|
import type { Agent } from "../src/agents/types";
|
||||||
|
|
||||||
|
const TEST_AGENTS_DIR = path.join(__dirname, "../src/agents");
|
||||||
|
const TEST_OUTPUT_DIR = "/tmp/test-agents";
|
||||||
|
|
||||||
|
describe("spawn-agent", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Create test output directory
|
||||||
|
if (!fs.existsSync(TEST_OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(TEST_OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Clean up test files
|
||||||
|
if (fs.existsSync(TEST_OUTPUT_DIR)) {
|
||||||
|
const files = fs.readdirSync(TEST_OUTPUT_DIR);
|
||||||
|
for (const file of files) {
|
||||||
|
fs.unlinkSync(path.join(TEST_OUTPUT_DIR, file));
|
||||||
|
}
|
||||||
|
fs.rmdirSync(TEST_OUTPUT_DIR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("spawnAgent", () => {
|
||||||
|
it("should create an agent with correct ID from name", () => {
|
||||||
|
const agent = spawnAgent("Test Agent");
|
||||||
|
expect(agent.id).toBe("test-agent");
|
||||||
|
expect(agent.name).toBe("Test Agent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should infer scribe role from name", () => {
|
||||||
|
const agent = spawnAgent("scribe-support");
|
||||||
|
expect(agent.role).toBe("scribe");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should infer qa role from name", () => {
|
||||||
|
const agent = spawnAgent("qa-validator");
|
||||||
|
expect(agent.role).toBe("qa");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should infer archive role from name", () => {
|
||||||
|
const agent = spawnAgent("archive-manager");
|
||||||
|
expect(agent.role).toBe("archive");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use custom role when specified", () => {
|
||||||
|
const agent = spawnAgent("my-agent", { role: "guardian" });
|
||||||
|
expect(agent.role).toBe("guardian");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use custom description when provided", () => {
|
||||||
|
const agent = spawnAgent("my-agent", { description: "Custom description" });
|
||||||
|
expect(agent.description).toBe("Custom description");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include parent agent reference", () => {
|
||||||
|
const agent = spawnAgent("child-agent", { parent: "parent-agent" });
|
||||||
|
expect(agent.parentAgent).toBe("parent-agent");
|
||||||
|
expect(agent.metadata.createdBy).toContain("spawned-by");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include custom traits", () => {
|
||||||
|
const agent = spawnAgent("my-agent", { traits: ["fast", "reliable"] });
|
||||||
|
expect(agent.traits).toContain("fast");
|
||||||
|
expect(agent.traits).toContain("reliable");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include base capabilities", () => {
|
||||||
|
const agent = spawnAgent("my-agent");
|
||||||
|
const capabilityNames = agent.capabilities.map(c => c.name);
|
||||||
|
expect(capabilityNames).toContain("self-describe");
|
||||||
|
expect(capabilityNames).toContain("issue-link");
|
||||||
|
expect(capabilityNames).toContain("pr-link");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include role-specific triggers", () => {
|
||||||
|
const agent = spawnAgent("scribe-agent");
|
||||||
|
const triggerEmojis = agent.triggers.map(t => t.emoji);
|
||||||
|
expect(triggerEmojis).toContain("📝");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include custom emoji trigger", () => {
|
||||||
|
const agent = spawnAgent("my-agent", { emoji: "🔮" });
|
||||||
|
const triggerEmojis = agent.triggers.map(t => t.emoji);
|
||||||
|
expect(triggerEmojis).toContain("🔮");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include metadata with timestamp", () => {
|
||||||
|
const agent = spawnAgent("my-agent");
|
||||||
|
expect(agent.metadata.createdAt).toBeDefined();
|
||||||
|
expect(agent.metadata.version).toBe("1.0.0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveAgent", () => {
|
||||||
|
it("should save agent to JSON file", () => {
|
||||||
|
const agent = spawnAgent("test-save-agent");
|
||||||
|
const filePath = saveAgent(agent, TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
expect(fs.existsSync(filePath)).toBe(true);
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const savedAgent = JSON.parse(content);
|
||||||
|
expect(savedAgent.id).toBe("test-save-agent");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AgentBuilder", () => {
|
||||||
|
it("should build agent with fluent API", () => {
|
||||||
|
const agent = createAgent("Custom Agent")
|
||||||
|
.withRole("guardian")
|
||||||
|
.withDescription("A custom guardian agent")
|
||||||
|
.withTraits("vigilant", "protective")
|
||||||
|
.withTrigger("🛡️", "protect")
|
||||||
|
.withCapability("monitoring", "Monitors the system", true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
expect(agent.id).toBe("custom-agent");
|
||||||
|
expect(agent.name).toBe("Custom Agent");
|
||||||
|
expect(agent.role).toBe("guardian");
|
||||||
|
expect(agent.description).toBe("A custom guardian agent");
|
||||||
|
expect(agent.traits).toContain("vigilant");
|
||||||
|
expect(agent.triggers.some(t => t.emoji === "🛡️")).toBe(true);
|
||||||
|
expect(agent.capabilities.some(c => c.name === "monitoring")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set parent agent", () => {
|
||||||
|
const agent = createAgent("Child Agent")
|
||||||
|
.withRole("support")
|
||||||
|
.withParent("parent-id")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
expect(agent.parentAgent).toBe("parent-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add default capabilities if none provided", () => {
|
||||||
|
const agent = createAgent("Simple Agent")
|
||||||
|
.withRole("custom")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const capabilityNames = agent.capabilities.map(c => c.name);
|
||||||
|
expect(capabilityNames).toContain("self-describe");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AgentRegistry", () => {
|
||||||
|
it("should load existing agents", () => {
|
||||||
|
const registry = new AgentRegistry(TEST_AGENTS_DIR);
|
||||||
|
const agents = registry.loadAll();
|
||||||
|
|
||||||
|
expect(agents.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Should have loaded the predefined agents
|
||||||
|
const agentIds = agents.map(a => a.id);
|
||||||
|
expect(agentIds).toContain("scribe-agent");
|
||||||
|
expect(agentIds).toContain("qa-agent");
|
||||||
|
expect(agentIds).toContain("guardian-agent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get agent by ID", () => {
|
||||||
|
const registry = new AgentRegistry(TEST_AGENTS_DIR);
|
||||||
|
const agent = registry.get("scribe-agent");
|
||||||
|
|
||||||
|
expect(agent).toBeDefined();
|
||||||
|
expect(agent?.role).toBe("scribe");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should find agents by role", () => {
|
||||||
|
const registry = new AgentRegistry(TEST_AGENTS_DIR);
|
||||||
|
registry.loadAll();
|
||||||
|
const qaAgents = registry.findByRole("qa");
|
||||||
|
|
||||||
|
expect(qaAgents.length).toBeGreaterThan(0);
|
||||||
|
expect(qaAgents[0].role).toBe("qa");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should find agents by trigger emoji", () => {
|
||||||
|
const registry = new AgentRegistry(TEST_AGENTS_DIR);
|
||||||
|
registry.loadAll();
|
||||||
|
const agents = registry.findByTrigger("📝");
|
||||||
|
|
||||||
|
expect(agents.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should list all agent IDs", () => {
|
||||||
|
const registry = new AgentRegistry(TEST_AGENTS_DIR);
|
||||||
|
const ids = registry.list();
|
||||||
|
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
expect(ids).toContain("scribe-agent");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user