Merge commit 'af30304038efa8dbffec306506c70b511a0021c8'

This commit is contained in:
Alexa Amundson
2025-11-25 13:43:58 -06:00
13 changed files with 1240 additions and 0 deletions

View File

@@ -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": "",

View 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"
}
}

View 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": []
}

View 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
View 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";

View 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 };

View 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
View 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"
}
}

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

View 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
View 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
View 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");
});
});