mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-23 10:45:33 -04:00
fix(interfaces): resolve 8 critical bugs across web/tiara/persona layers
P0 Critical Fixes: - Fix import ./tiara → ./claude-flow in plugin/builtin/index.ts - Create AgentOrchestrator type bridge for council/tiara integration (src/tiara.ts) - Fix hardcoded state ID in memory-bridge.ts (was causing cross-persona data loss) P1 Core Functionality: - Add persona isolation to memory bridge namespace (personas now have private memory) - Load SKILL.md content into drone prompts (skills now available to drones) - Inject persona-specific tools from AGENT_CONFIGS into drone prompts P2 API Consistency: - Standardize error response format in server.ts (MCP & gateway endpoints) - WebSocket param validation already in place (verified) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -240,10 +240,10 @@ export namespace Server {
|
||||
async (c) => {
|
||||
const gateway = WhatsAppGateway.getInstance()
|
||||
if (!gateway) {
|
||||
return c.json({ success: false, error: "WhatsApp gateway not running" }, 400)
|
||||
return c.json({ data: null, errors: [{ message: "WhatsApp gateway not running" }], success: false }, 400)
|
||||
}
|
||||
if (!gateway.isReady()) {
|
||||
return c.json({ success: false, error: "WhatsApp not connected" }, 400)
|
||||
return c.json({ data: null, errors: [{ message: "WhatsApp not connected" }], success: false }, 400)
|
||||
}
|
||||
const { phone, message } = c.req.valid("json")
|
||||
const chatId = `${phone}@c.us`
|
||||
@@ -272,10 +272,10 @@ export namespace Server {
|
||||
async (c) => {
|
||||
const gateway = WhatsAppGateway.getInstance()
|
||||
if (!gateway) {
|
||||
return c.json({ success: false, error: "WhatsApp gateway not running" }, 400)
|
||||
return c.json({ data: null, errors: [{ message: "WhatsApp gateway not running" }], success: false }, 400)
|
||||
}
|
||||
if (!gateway.isReady()) {
|
||||
return c.json({ success: false, error: "WhatsApp not connected" }, 400)
|
||||
return c.json({ data: null, errors: [{ message: "WhatsApp not connected" }], success: false }, 400)
|
||||
}
|
||||
const { chatJid, messageId, emoji } = c.req.valid("json")
|
||||
const success = await gateway.sendReaction(chatJid, messageId, emoji)
|
||||
@@ -3197,7 +3197,7 @@ export namespace Server {
|
||||
const name = c.req.param("name")
|
||||
const supportsOAuth = await MCP.supportsOAuth(name)
|
||||
if (!supportsOAuth) {
|
||||
return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
|
||||
return c.json({ data: null, errors: [{ message: `MCP server ${name} does not support OAuth` }], success: false }, 400)
|
||||
}
|
||||
const result = await MCP.startAuth(name)
|
||||
return c.json(result)
|
||||
@@ -3257,7 +3257,7 @@ export namespace Server {
|
||||
const name = c.req.param("name")
|
||||
const supportsOAuth = await MCP.supportsOAuth(name)
|
||||
if (!supportsOAuth) {
|
||||
return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
|
||||
return c.json({ data: null, errors: [{ message: `MCP server ${name} does not support OAuth` }], success: false }, 400)
|
||||
}
|
||||
const status = await MCP.authenticate(name)
|
||||
return c.json(status)
|
||||
|
||||
@@ -318,9 +318,9 @@ export class ContinuityManager {
|
||||
// Save to memory
|
||||
await this.memoryBridge.saveConversationState(this.currentState);
|
||||
|
||||
// Store individual facts as memories for semantic search
|
||||
// Store individual facts as memories for semantic search (persona-isolated)
|
||||
if (newFacts.length > 0) {
|
||||
await this.memoryBridge.storeKeyFacts(newFacts, this.currentState.sessionId);
|
||||
await this.memoryBridge.storeKeyFacts(newFacts, this.currentState.sessionId, this.currentState.leadPersona);
|
||||
}
|
||||
|
||||
return this.currentState;
|
||||
|
||||
@@ -68,9 +68,13 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
private embedder: EmbeddingProvider;
|
||||
private config: PersonasConfig["qdrant"];
|
||||
private initialized = false;
|
||||
private instanceId: string;
|
||||
|
||||
constructor(config: PersonasConfig["qdrant"], options?: { useMockEmbeddings?: boolean }) {
|
||||
constructor(config: PersonasConfig["qdrant"], options?: { useMockEmbeddings?: boolean; instanceId?: string }) {
|
||||
this.config = config;
|
||||
// Generate a stable instance ID based on machine identity or use provided one
|
||||
// This prevents state collision when multiple orchestrators run
|
||||
this.instanceId = options?.instanceId ?? this.generateInstanceId();
|
||||
|
||||
// Initialize embedding provider
|
||||
// Use mock if no API key or explicitly requested
|
||||
@@ -116,6 +120,24 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a stable instance ID for this machine/user
|
||||
*/
|
||||
private generateInstanceId(): string {
|
||||
// Use hostname + username for a stable per-machine ID
|
||||
const os = require("os");
|
||||
const hostname = os.hostname() || "unknown";
|
||||
const username = os.userInfo().username || "user";
|
||||
return stringToUUID(`personas-${hostname}-${username}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state ID for this instance
|
||||
*/
|
||||
private getStateId(): string {
|
||||
return stringToUUID(`state-${this.instanceId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Personas state to Qdrant
|
||||
*/
|
||||
@@ -123,8 +145,8 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
await this.init();
|
||||
|
||||
const stateJson = JSON.stringify(state);
|
||||
// Use a fixed UUID for the current state (deterministic)
|
||||
const stateId = "00000000-0000-0000-0000-000000000001";
|
||||
// Use instance-specific UUID for state (prevents multi-instance collision)
|
||||
const stateId = this.getStateId();
|
||||
|
||||
// Generate embedding for the state summary
|
||||
const stateSummary = this.generateStateSummary(state);
|
||||
@@ -157,7 +179,7 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
async loadState(): Promise<PersonasState | null> {
|
||||
await this.init();
|
||||
|
||||
const stateId = "00000000-0000-0000-0000-000000000001";
|
||||
const stateId = this.getStateId();
|
||||
const results = await this.storage.get([stateId]);
|
||||
const result = results[0];
|
||||
|
||||
@@ -295,6 +317,7 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
|
||||
/**
|
||||
* Store a memory entry for semantic retrieval
|
||||
* Memories are isolated by persona namespace for privacy
|
||||
*/
|
||||
async storeMemory(
|
||||
content: string,
|
||||
@@ -302,12 +325,21 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
): Promise<string> {
|
||||
await this.init();
|
||||
|
||||
// Require persona for proper isolation
|
||||
const persona = metadata.persona as string;
|
||||
if (!persona) {
|
||||
throw new Error("Memory storage requires persona in metadata for isolation");
|
||||
}
|
||||
|
||||
// Use persona-specific namespace for isolation
|
||||
const namespace = `personas:${persona}`;
|
||||
|
||||
const memory = await this.memoryStore.save({
|
||||
content,
|
||||
category: "context",
|
||||
source: "agent",
|
||||
senderId: (metadata.persona as string) ?? "personas",
|
||||
namespace: "personas",
|
||||
senderId: persona,
|
||||
namespace,
|
||||
metadata,
|
||||
});
|
||||
|
||||
@@ -316,16 +348,24 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
|
||||
/**
|
||||
* Search memories by semantic similarity
|
||||
* Can search within a specific persona's namespace or across all personas
|
||||
*/
|
||||
async searchMemories(
|
||||
query: string,
|
||||
limit = 10
|
||||
limit = 10,
|
||||
options?: { persona?: string; includeShared?: boolean }
|
||||
): Promise<Array<{ id: string; content: string; score: number }>> {
|
||||
await this.init();
|
||||
|
||||
// If persona specified, search only that persona's namespace
|
||||
// Otherwise search the shared namespace
|
||||
const namespace = options?.persona
|
||||
? `personas:${options.persona}`
|
||||
: "personas:shared";
|
||||
|
||||
const results = await this.memoryStore.search(query, {
|
||||
limit,
|
||||
namespace: "personas",
|
||||
namespace,
|
||||
});
|
||||
|
||||
return results.map((r) => ({
|
||||
@@ -335,6 +375,28 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search memories across all personas (for cross-persona context)
|
||||
*/
|
||||
async searchAllPersonaMemories(
|
||||
query: string,
|
||||
limit = 10
|
||||
): Promise<Array<{ id: string; content: string; score: number; persona?: string }>> {
|
||||
await this.init();
|
||||
|
||||
// Search without namespace filter to get all memories
|
||||
const results = await this.memoryStore.search(query, {
|
||||
limit,
|
||||
});
|
||||
|
||||
return results.map((r) => ({
|
||||
id: r.id,
|
||||
content: r.content,
|
||||
score: r.score,
|
||||
persona: r.senderId,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memories by IDs
|
||||
*/
|
||||
@@ -355,13 +417,14 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
/**
|
||||
* Store key facts extracted from conversation
|
||||
*/
|
||||
async storeKeyFacts(facts: string[], sessionId: string): Promise<void> {
|
||||
async storeKeyFacts(facts: string[], sessionId: string, persona: string): Promise<void> {
|
||||
await this.init();
|
||||
|
||||
for (const fact of facts) {
|
||||
await this.storeMemory(fact, {
|
||||
type: "key_fact",
|
||||
sessionId,
|
||||
persona,
|
||||
extractedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
@@ -376,6 +439,7 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
limit?: number;
|
||||
includeKeyFacts?: boolean;
|
||||
sessionId?: string;
|
||||
persona?: string;
|
||||
}
|
||||
): Promise<{
|
||||
relevantMemories: Array<{ content: string; score: number }>;
|
||||
@@ -385,8 +449,10 @@ export class QdrantMemoryBridge implements MemoryBridge {
|
||||
|
||||
const limit = options?.limit ?? 5;
|
||||
|
||||
// Search for relevant memories
|
||||
const memories = await this.searchMemories(taskDescription, limit);
|
||||
// Search for relevant memories (persona-specific if provided)
|
||||
const memories = await this.searchMemories(taskDescription, limit, {
|
||||
persona: options?.persona,
|
||||
});
|
||||
|
||||
// Load conversation state if session ID provided
|
||||
let conversationState: ConversationState | undefined;
|
||||
|
||||
@@ -7,6 +7,207 @@
|
||||
|
||||
import type { PersonaId, OrchestrationPersona } from "./types";
|
||||
import { ORCHESTRATION_PERSONAS } from "./types";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
|
||||
// Import AGENT_CONFIGS for tool injection
|
||||
import { AGENT_CONFIGS } from "../agent/personas";
|
||||
|
||||
// =============================================================================
|
||||
// Skill Loading
|
||||
// =============================================================================
|
||||
|
||||
interface SkillFrontmatter {
|
||||
name: string;
|
||||
description?: string;
|
||||
includes?: string[];
|
||||
}
|
||||
|
||||
interface LoadedSkill {
|
||||
frontmatter: SkillFrontmatter;
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Cache for loaded skills
|
||||
const skillCache = new Map<string, LoadedSkill>();
|
||||
|
||||
/**
|
||||
* Find the skills directory (supports both dev and installed paths)
|
||||
*/
|
||||
function findSkillsDir(): string {
|
||||
// Try relative paths from common locations
|
||||
const candidates = [
|
||||
join(process.cwd(), ".claude", "skills"),
|
||||
join(dirname(process.execPath), "..", "..", ".claude", "skills"),
|
||||
join(process.env.HOME || "", ".local", "src", "agent-core", ".claude", "skills"),
|
||||
];
|
||||
|
||||
for (const dir of candidates) {
|
||||
if (existsSync(dir)) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to cwd
|
||||
return join(process.cwd(), ".claude", "skills");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse YAML frontmatter from markdown content
|
||||
*/
|
||||
function parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {
|
||||
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||||
if (!match) {
|
||||
return { frontmatter: { name: "unknown" }, body: content };
|
||||
}
|
||||
|
||||
const yamlContent = match[1];
|
||||
const body = match[2];
|
||||
|
||||
// Simple YAML parsing for our use case
|
||||
const frontmatter: SkillFrontmatter = { name: "unknown" };
|
||||
const lines = yamlContent.split("\n");
|
||||
let currentKey = "";
|
||||
let inArray = false;
|
||||
const arrayValues: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
|
||||
// Check for array item
|
||||
if (trimmed.startsWith("- ") && inArray) {
|
||||
arrayValues.push(trimmed.slice(2).trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we were collecting an array, save it
|
||||
if (inArray && currentKey === "includes") {
|
||||
frontmatter.includes = [...arrayValues];
|
||||
arrayValues.length = 0;
|
||||
inArray = false;
|
||||
}
|
||||
|
||||
// Check for key: value
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
if (colonIndex > 0) {
|
||||
currentKey = trimmed.slice(0, colonIndex).trim();
|
||||
const value = trimmed.slice(colonIndex + 1).trim();
|
||||
|
||||
if (value) {
|
||||
// Simple value
|
||||
if (currentKey === "name") frontmatter.name = value;
|
||||
else if (currentKey === "description") frontmatter.description = value;
|
||||
} else {
|
||||
// Start of array or empty
|
||||
if (currentKey === "includes") {
|
||||
inArray = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where array is at the end
|
||||
if (inArray && currentKey === "includes" && arrayValues.length > 0) {
|
||||
frontmatter.includes = [...arrayValues];
|
||||
}
|
||||
|
||||
return { frontmatter, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a skill by name (handles includes recursively)
|
||||
*/
|
||||
function loadSkill(skillName: string, skillsDir: string, loaded = new Set<string>()): LoadedSkill | null {
|
||||
// Prevent infinite loops
|
||||
if (loaded.has(skillName)) {
|
||||
return null;
|
||||
}
|
||||
loaded.add(skillName);
|
||||
|
||||
// Check cache
|
||||
if (skillCache.has(skillName)) {
|
||||
return skillCache.get(skillName)!;
|
||||
}
|
||||
|
||||
// Try to load the skill file
|
||||
const skillPath = join(skillsDir, skillName, "SKILL.md");
|
||||
if (!existsSync(skillPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(skillPath, "utf-8");
|
||||
const { frontmatter, body } = parseFrontmatter(content);
|
||||
|
||||
// Load included skills
|
||||
let combinedContent = body;
|
||||
if (frontmatter.includes?.length) {
|
||||
const includedParts: string[] = [];
|
||||
for (const includeName of frontmatter.includes) {
|
||||
const included = loadSkill(includeName, skillsDir, loaded);
|
||||
if (included) {
|
||||
includedParts.push(`\n<!-- Included from ${includeName} -->\n${included.content}`);
|
||||
}
|
||||
}
|
||||
if (includedParts.length > 0) {
|
||||
combinedContent = body + "\n" + includedParts.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
const skill: LoadedSkill = {
|
||||
frontmatter,
|
||||
content: combinedContent,
|
||||
};
|
||||
|
||||
skillCache.set(skillName, skill);
|
||||
return skill;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loaded skill content for a persona
|
||||
*/
|
||||
export function getPersonaSkillContent(persona: PersonaId): string | null {
|
||||
const skillsDir = findSkillsDir();
|
||||
const skill = loadSkill(persona, skillsDir);
|
||||
return skill?.content ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the skill cache (useful for reloading)
|
||||
*/
|
||||
export function clearSkillCache(): void {
|
||||
skillCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get persona-specific tools from AGENT_CONFIGS
|
||||
*/
|
||||
export function getPersonaTools(persona: PersonaId): string[] {
|
||||
const config = AGENT_CONFIGS[persona];
|
||||
if (!config?.tools) return [];
|
||||
|
||||
// Extract domain-specific tools (those with persona prefix)
|
||||
const domainTools: string[] = [];
|
||||
for (const [tool, enabled] of Object.entries(config.tools)) {
|
||||
if (enabled && tool.includes(":")) {
|
||||
domainTools.push(tool);
|
||||
}
|
||||
}
|
||||
|
||||
return domainTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full tool configuration for a persona
|
||||
*/
|
||||
export function getPersonaToolConfig(persona: PersonaId): Record<string, boolean> {
|
||||
const config = AGENT_CONFIGS[persona];
|
||||
return config?.tools ?? {};
|
||||
}
|
||||
|
||||
// Import tiara types
|
||||
// Note: These are from the vendor submodule
|
||||
@@ -154,6 +355,7 @@ export function selectDroneType(
|
||||
|
||||
/**
|
||||
* Generate a system prompt for a persona drone.
|
||||
* Includes skill content from SKILL.md files.
|
||||
*/
|
||||
export function generateDronePrompt(
|
||||
persona: PersonaId,
|
||||
@@ -162,6 +364,7 @@ export function generateDronePrompt(
|
||||
plan?: string;
|
||||
objectives?: string[];
|
||||
keyFacts?: string[];
|
||||
includeSkills?: boolean;
|
||||
}
|
||||
): string {
|
||||
const config = getPersonaConfig(persona);
|
||||
@@ -180,6 +383,30 @@ export function generateDronePrompt(
|
||||
});
|
||||
parts.push(``);
|
||||
|
||||
// Load and include skill content (if enabled, default true)
|
||||
const includeSkills = context?.includeSkills !== false;
|
||||
if (includeSkills) {
|
||||
parts.push(`# Capabilities and Tools\n`);
|
||||
|
||||
// Include domain-specific tools from AGENT_CONFIGS
|
||||
const domainTools = getPersonaTools(persona);
|
||||
if (domainTools.length > 0) {
|
||||
parts.push(`## Available Domain Tools`);
|
||||
for (const tool of domainTools) {
|
||||
parts.push(`- \`${tool}\``);
|
||||
}
|
||||
parts.push(``);
|
||||
}
|
||||
|
||||
// Include condensed skill content from SKILL.md
|
||||
const skillContent = getPersonaSkillContent(persona);
|
||||
if (skillContent) {
|
||||
const condensed = condenseSkillContent(skillContent, persona);
|
||||
parts.push(condensed);
|
||||
parts.push(``);
|
||||
}
|
||||
}
|
||||
|
||||
// Context from queen
|
||||
if (context?.plan) {
|
||||
parts.push(`# Current Plan`);
|
||||
@@ -218,6 +445,42 @@ export function generateDronePrompt(
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Condense skill content for drone prompts (extract key sections)
|
||||
*/
|
||||
function condenseSkillContent(content: string, persona: PersonaId): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
// Extract Domain Tools section if present
|
||||
const toolsMatch = content.match(/## Domain Tools[\s\S]*?\n\n(?=##|$)/);
|
||||
if (toolsMatch) {
|
||||
parts.push(toolsMatch[0].trim());
|
||||
}
|
||||
|
||||
// Extract Core Capabilities section if present
|
||||
const capsMatch = content.match(/## Core Capabilities[\s\S]*?\n\n(?=##|$)/);
|
||||
if (capsMatch) {
|
||||
// Just get the headers, not all the code blocks
|
||||
const capsContent = capsMatch[0];
|
||||
const headers = capsContent.match(/### [^\n]+/g);
|
||||
if (headers) {
|
||||
parts.push(`Available capabilities: ${headers.map(h => h.replace("### ", "")).join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If no structured content found, provide a summary based on persona
|
||||
if (parts.length === 0) {
|
||||
const summaries: Record<PersonaId, string> = {
|
||||
zee: "Memory storage/search, messaging (WhatsApp/Telegram), calendar, contacts, notifications",
|
||||
stanley: "Market data, portfolio management, SEC filings, research, NautilusTrader backtesting",
|
||||
johny: "Knowledge graph, spaced repetition, concept mapping, practice problems, learning progress",
|
||||
};
|
||||
parts.push(`Tools: ${summaries[persona]}`);
|
||||
}
|
||||
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get persona config by ID
|
||||
*/
|
||||
|
||||
+16
-3
@@ -476,12 +476,25 @@ export interface MemoryBridge {
|
||||
/** Load state from Qdrant */
|
||||
loadState(): Promise<PersonasState | null>;
|
||||
|
||||
/** Store a memory for continuity */
|
||||
/** Store a memory for continuity (requires persona in metadata for isolation) */
|
||||
storeMemory(content: string, metadata: Record<string, unknown>): Promise<string>;
|
||||
|
||||
/** Search memories by query */
|
||||
searchMemories(query: string, limit?: number): Promise<Array<{ id: string; content: string; score: number }>>;
|
||||
/** Search memories by query (persona-isolated unless searching shared namespace) */
|
||||
searchMemories(
|
||||
query: string,
|
||||
limit?: number,
|
||||
options?: { persona?: string; includeShared?: boolean }
|
||||
): Promise<Array<{ id: string; content: string; score: number }>>;
|
||||
|
||||
/** Search memories across all personas (for cross-persona context) */
|
||||
searchAllPersonaMemories?(
|
||||
query: string,
|
||||
limit?: number
|
||||
): Promise<Array<{ id: string; content: string; score: number; persona?: string }>>;
|
||||
|
||||
/** Get memories by IDs */
|
||||
getMemories(ids: string[]): Promise<Array<{ id: string; content: string }>>;
|
||||
|
||||
/** Store key facts (requires persona for isolation) */
|
||||
storeKeyFacts?(facts: string[], sessionId: string, persona: string): Promise<void>;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Exports all built-in plugins for easy registration.
|
||||
*/
|
||||
|
||||
export { ClaudeFlowPlugin } from './tiara';
|
||||
export { ClaudeFlowPlugin } from './claude-flow';
|
||||
export { AnthropicAuthPlugin } from './anthropic-auth';
|
||||
export { CopilotAuthPlugin } from './copilot-auth';
|
||||
export { MemoryPersistencePlugin } from './memory-persistence';
|
||||
@@ -13,7 +13,7 @@ export { MemoryPersistencePlugin } from './memory-persistence';
|
||||
export { StanleyFinancePlugin } from './domains/stanley-finance';
|
||||
export { ZeeMessagingPlugin } from './domains/zee-messaging';
|
||||
|
||||
import { ClaudeFlowPlugin } from './tiara';
|
||||
import { ClaudeFlowPlugin } from './claude-flow';
|
||||
import { AnthropicAuthPlugin } from './anthropic-auth';
|
||||
import { CopilotAuthPlugin } from './copilot-auth';
|
||||
import { MemoryPersistencePlugin } from './memory-persistence';
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Tiara - Agent Orchestration Interface
|
||||
*
|
||||
* This module bridges the Council deliberation system with the Personas orchestrator.
|
||||
* It exports the AgentOrchestrator interface expected by the council stages.
|
||||
*/
|
||||
|
||||
import type { PersonaId } from "./personas/types";
|
||||
|
||||
/**
|
||||
* Result from spawning an agent
|
||||
*/
|
||||
export interface AgentSpawnResult {
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for spawning an agent
|
||||
*/
|
||||
export interface AgentSpawnOptions {
|
||||
agentType: string;
|
||||
action: string;
|
||||
params: Record<string, unknown>;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AgentOrchestrator interface expected by the Council deliberation system.
|
||||
* Provides agent spawning capabilities for multi-agent coordination.
|
||||
*/
|
||||
export interface AgentOrchestrator {
|
||||
/**
|
||||
* Spawn an agent to perform a specific action
|
||||
*/
|
||||
spawnAgent(options: AgentSpawnOptions): Promise<AgentSpawnResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map council agent types to personas
|
||||
*/
|
||||
function mapAgentTypeToPersona(agentType: string): PersonaId {
|
||||
switch (agentType.toLowerCase()) {
|
||||
case "inbox_manager":
|
||||
case "scheduler":
|
||||
case "task_coordinator":
|
||||
return "zee";
|
||||
case "research_assistant":
|
||||
return "johny";
|
||||
case "market_analyst":
|
||||
case "portfolio_manager":
|
||||
return "stanley";
|
||||
default:
|
||||
return "zee"; // Default to Zee for unknown types
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AgentOrchestrator adapter from the Personas Orchestrator
|
||||
*/
|
||||
export function createAgentOrchestrator(
|
||||
orchestrator: import("./personas/tiara").Orchestrator
|
||||
): AgentOrchestrator {
|
||||
return {
|
||||
async spawnAgent(options: AgentSpawnOptions): Promise<AgentSpawnResult> {
|
||||
const startTime = Date.now();
|
||||
const persona = mapAgentTypeToPersona(options.agentType);
|
||||
|
||||
try {
|
||||
// Build prompt from action and params
|
||||
const prompt = buildAgentPrompt(options);
|
||||
|
||||
// Use spawnDroneWithWait to get the result
|
||||
const result = await orchestrator.spawnDroneWithWait({
|
||||
persona,
|
||||
task: `${options.agentType}: ${options.action}`,
|
||||
prompt,
|
||||
});
|
||||
|
||||
return {
|
||||
result: result.result,
|
||||
error: result.error,
|
||||
durationMs: result.durationMs,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
durationMs: Date.now() - startTime,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a prompt for the agent from spawn options
|
||||
*/
|
||||
function buildAgentPrompt(options: AgentSpawnOptions): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
parts.push(`## Agent Role: ${options.agentType}`);
|
||||
parts.push(`## Action: ${options.action}`);
|
||||
|
||||
if (options.context) {
|
||||
parts.push(`\n## Context\n${options.context}`);
|
||||
}
|
||||
|
||||
if (options.params.query) {
|
||||
parts.push(`\n## Query\n${options.params.query}`);
|
||||
}
|
||||
|
||||
// Add any other params
|
||||
const otherParams = Object.entries(options.params).filter(
|
||||
([key]) => key !== "query"
|
||||
);
|
||||
if (otherParams.length > 0) {
|
||||
parts.push(`\n## Parameters`);
|
||||
for (const [key, value] of otherParams) {
|
||||
parts.push(`- ${key}: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
// Re-export Orchestrator for convenience
|
||||
export { Orchestrator } from "./personas/tiara";
|
||||
export type { PersonaId } from "./personas/types";
|
||||
Reference in New Issue
Block a user