refactor: streamline core API surface
This commit is contained in:
parent
0d54da1471
commit
75ae282861
12 changed files with 4 additions and 3402 deletions
|
|
@ -88,7 +88,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
private _extractor?: NeuralEntityExtractor
|
||||
private _tripleIntelligence?: TripleIntelligenceSystem
|
||||
private _vfs?: VirtualFileSystem
|
||||
private _conversation?: any // ConversationManager (lazy-loaded)
|
||||
|
||||
// State
|
||||
private initialized = false
|
||||
|
|
@ -1671,31 +1670,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
return this._vfs
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation Manager API - Infinite Agent Memory
|
||||
*
|
||||
* Provides conversation and context management for AI agents:
|
||||
* - Save and retrieve conversation messages
|
||||
* - Semantic search across conversation history
|
||||
* - Smart context retrieval with relevance ranking
|
||||
* - Artifact management (code, files, documents)
|
||||
* - Conversation themes and clustering
|
||||
*
|
||||
* @returns ConversationManager instance
|
||||
* @example
|
||||
* const conv = brain.conversation
|
||||
* await conv.saveMessage("How do I implement auth?", "user", { conversationId: "conv_123" })
|
||||
* const context = await conv.getRelevantContext("authentication implementation")
|
||||
*/
|
||||
conversation() {
|
||||
if (!this._conversation) {
|
||||
// Lazy-load ConversationManager to avoid circular dependencies
|
||||
const { ConversationManager } = require('./conversation/conversationManager.js')
|
||||
this._conversation = new ConversationManager(this)
|
||||
}
|
||||
return this._conversation
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Management API - backup, restore, import, export
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,637 +0,0 @@
|
|||
/**
|
||||
* 💬 Conversation CLI Commands
|
||||
*
|
||||
* CLI interface for infinite agent memory and conversation management
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer'
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import * as fs from '../../universal/fs.js'
|
||||
import * as path from '../../universal/path.js'
|
||||
import { Brainy } from '../../brainy.js'
|
||||
|
||||
interface CommandArguments {
|
||||
action?: string
|
||||
conversationId?: string
|
||||
query?: string
|
||||
role?: string
|
||||
limit?: number
|
||||
format?: string
|
||||
output?: string
|
||||
_: string[]
|
||||
}
|
||||
|
||||
export const conversationCommand = {
|
||||
command: 'conversation [action]',
|
||||
describe: '💬 Conversation and context management',
|
||||
|
||||
builder: (yargs: any) => {
|
||||
return yargs
|
||||
.positional('action', {
|
||||
describe: 'Conversation operation to perform',
|
||||
type: 'string',
|
||||
choices: ['setup', 'remove', 'search', 'context', 'thread', 'stats', 'export', 'import']
|
||||
})
|
||||
.option('conversation-id', {
|
||||
describe: 'Conversation ID',
|
||||
type: 'string',
|
||||
alias: 'c'
|
||||
})
|
||||
.option('query', {
|
||||
describe: 'Search query or context query',
|
||||
type: 'string',
|
||||
alias: 'q'
|
||||
})
|
||||
.option('role', {
|
||||
describe: 'Filter by message role',
|
||||
type: 'string',
|
||||
choices: ['user', 'assistant', 'system', 'tool'],
|
||||
alias: 'r'
|
||||
})
|
||||
.option('limit', {
|
||||
describe: 'Maximum results',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
alias: 'l'
|
||||
})
|
||||
.option('format', {
|
||||
describe: 'Output format',
|
||||
type: 'string',
|
||||
choices: ['json', 'table', 'text'],
|
||||
default: 'table',
|
||||
alias: 'f'
|
||||
})
|
||||
.option('output', {
|
||||
describe: 'Output file path',
|
||||
type: 'string',
|
||||
alias: 'o'
|
||||
})
|
||||
.example('$0 conversation setup', 'Set up MCP server for Claude Code')
|
||||
.example('$0 conversation search -q "authentication" -l 5', 'Search messages')
|
||||
.example('$0 conversation context -q "how to implement JWT"', 'Get relevant context')
|
||||
.example('$0 conversation thread -c conv_123', 'Get conversation thread')
|
||||
.example('$0 conversation stats', 'Show conversation statistics')
|
||||
},
|
||||
|
||||
handler: async (argv: CommandArguments) => {
|
||||
const action = argv.action || 'setup'
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'setup':
|
||||
await handleSetup(argv)
|
||||
break
|
||||
case 'remove':
|
||||
await handleRemove(argv)
|
||||
break
|
||||
case 'search':
|
||||
await handleSearch(argv)
|
||||
break
|
||||
case 'context':
|
||||
await handleContext(argv)
|
||||
break
|
||||
case 'thread':
|
||||
await handleThread(argv)
|
||||
break
|
||||
case 'stats':
|
||||
await handleStats(argv)
|
||||
break
|
||||
case 'export':
|
||||
await handleExport(argv)
|
||||
break
|
||||
case 'import':
|
||||
await handleImport(argv)
|
||||
break
|
||||
default:
|
||||
console.log(chalk.yellow(`Unknown action: ${action}`))
|
||||
console.log('Run "brainy conversation --help" for usage information')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`Error: ${error.message}`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle setup command - Set up MCP server for Claude Code
|
||||
*/
|
||||
async function handleSetup(argv: CommandArguments) {
|
||||
console.log(chalk.bold.cyan('\n🧠 Brainy Infinite Memory Setup\n'))
|
||||
|
||||
// Check for existing setup
|
||||
const homeDir = process.env.HOME || process.env.USERPROFILE || '~'
|
||||
const brainyDir = path.join(homeDir, '.brainy-memory')
|
||||
const dataDir = path.join(brainyDir, 'data')
|
||||
const serverPath = path.join(brainyDir, 'mcp-server.js')
|
||||
const configPath = path.join(homeDir, '.config', 'claude-code', 'mcp-servers.json')
|
||||
|
||||
// Check if already set up
|
||||
if (await fs.exists(brainyDir)) {
|
||||
const { overwrite } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'overwrite',
|
||||
message: 'Brainy memory setup already exists. Overwrite?',
|
||||
default: false
|
||||
}
|
||||
])
|
||||
|
||||
if (!overwrite) {
|
||||
console.log(chalk.yellow('Setup cancelled'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const spinner = ora('Creating Brainy memory directory...').start()
|
||||
|
||||
try {
|
||||
// Create directories
|
||||
await fs.mkdir(brainyDir, { recursive: true })
|
||||
await fs.mkdir(dataDir, { recursive: true })
|
||||
|
||||
spinner.succeed('Created Brainy memory directory')
|
||||
|
||||
// Create MCP server script
|
||||
spinner.start('Creating MCP server script...')
|
||||
|
||||
const serverScript = `#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Brainy Infinite Memory MCP Server
|
||||
*
|
||||
* This server provides conversation and context management
|
||||
* for Claude Code through the Model Control Protocol (MCP).
|
||||
*/
|
||||
|
||||
import { Brainy } from '@soulcraft/brainy'
|
||||
import { BrainyMCPService } from '@soulcraft/brainy'
|
||||
import { MCPConversationToolset } from '@soulcraft/brainy'
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Initialize Brainy with filesystem storage
|
||||
const brain = new Brainy({
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
path: '${dataDir.replace(/\\/g, '/')}'
|
||||
},
|
||||
silent: true // Suppress console output
|
||||
})
|
||||
|
||||
await brain.init()
|
||||
|
||||
// Create MCP service
|
||||
const mcpService = new BrainyMCPService(brain, {
|
||||
enableAuth: false // Local usage, no auth needed
|
||||
})
|
||||
|
||||
// Create conversation toolset
|
||||
const conversationTools = new MCPConversationToolset(brain)
|
||||
await conversationTools.init()
|
||||
|
||||
// Register conversation tools
|
||||
const tools = await conversationTools.getAvailableTools()
|
||||
|
||||
console.error('🧠 Brainy Memory Server started')
|
||||
console.error(\`📊 \${tools.length} conversation tools available\`)
|
||||
console.error('✅ Ready for Claude Code integration')
|
||||
|
||||
// Handle MCP requests via stdio
|
||||
process.stdin.on('data', async (data) => {
|
||||
try {
|
||||
const request = JSON.parse(data.toString())
|
||||
|
||||
// Route conversation tool requests
|
||||
let response
|
||||
if (request.toolName && request.toolName.startsWith('conversation_')) {
|
||||
response = await conversationTools.handleRequest(request)
|
||||
} else {
|
||||
response = await mcpService.handleRequest(request)
|
||||
}
|
||||
|
||||
// Write response to stdout
|
||||
process.stdout.write(JSON.stringify(response) + '\\n')
|
||||
} catch (error) {
|
||||
console.error('Error handling request:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Handle shutdown gracefully
|
||||
process.on('SIGINT', () => {
|
||||
console.error('\\n🛑 Shutting down Brainy Memory Server')
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to start Brainy Memory Server:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
`
|
||||
|
||||
await fs.writeFile(serverPath, serverScript, 'utf8')
|
||||
// Make executable on Unix systems
|
||||
try {
|
||||
await import('fs').then(fsModule => {
|
||||
fsModule.promises.chmod(serverPath, 0o755).catch(() => {})
|
||||
})
|
||||
} catch {
|
||||
// Windows doesn't need chmod
|
||||
}
|
||||
spinner.succeed('Created MCP server script')
|
||||
|
||||
// Create Claude Code config
|
||||
spinner.start('Configuring Claude Code...')
|
||||
|
||||
const configDir = path.dirname(configPath)
|
||||
await fs.mkdir(configDir, { recursive: true })
|
||||
|
||||
let mcpConfig: any = {}
|
||||
if (await fs.exists(configPath)) {
|
||||
const existingConfig = await fs.readFile(configPath, 'utf8')
|
||||
mcpConfig = JSON.parse(existingConfig)
|
||||
}
|
||||
|
||||
mcpConfig['brainy-memory'] = {
|
||||
command: 'node',
|
||||
args: [serverPath],
|
||||
env: {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf8')
|
||||
spinner.succeed('Configured Claude Code')
|
||||
|
||||
// Initialize Brainy database
|
||||
spinner.start('Initializing Brainy database...')
|
||||
|
||||
const brain = new Brainy({
|
||||
storage: {
|
||||
type: 'filesystem',
|
||||
options: {
|
||||
path: dataDir
|
||||
}
|
||||
},
|
||||
silent: true
|
||||
})
|
||||
await brain.init()
|
||||
|
||||
spinner.succeed('Initialized Brainy database')
|
||||
|
||||
// Shutdown Brainy to release resources
|
||||
await brain.close()
|
||||
|
||||
// Success!
|
||||
console.log(chalk.bold.green('\n✅ Setup complete!\n'))
|
||||
console.log(chalk.cyan('📁 Memory storage:'), brainyDir)
|
||||
console.log(chalk.cyan('🔧 MCP server:'), serverPath)
|
||||
console.log(chalk.cyan('⚙️ Claude Code config:'), configPath)
|
||||
console.log()
|
||||
console.log(chalk.bold('🚀 Next steps:'))
|
||||
console.log(' 1. Restart Claude Code to load the MCP server')
|
||||
console.log(' 2. Start a new conversation - your history will be saved automatically!')
|
||||
console.log(' 3. Claude will use past context to help you work faster')
|
||||
console.log()
|
||||
console.log(chalk.dim('Run "brainy conversation stats" to see your conversation statistics'))
|
||||
|
||||
} catch (error: any) {
|
||||
spinner.fail('Setup failed')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle remove command - Remove MCP server and optionally data
|
||||
*/
|
||||
async function handleRemove(argv: CommandArguments) {
|
||||
console.log(chalk.bold.cyan('\n🗑️ Brainy Infinite Memory Removal\n'))
|
||||
|
||||
const homeDir = process.env.HOME || process.env.USERPROFILE || '~'
|
||||
const brainyDir = path.join(homeDir, '.brainy-memory')
|
||||
const configPath = path.join(homeDir, '.config', 'claude-code', 'mcp-servers.json')
|
||||
|
||||
// Check if setup exists
|
||||
const brainyExists = await fs.exists(brainyDir)
|
||||
const configExists = await fs.exists(configPath)
|
||||
|
||||
if (!brainyExists && !configExists) {
|
||||
console.log(chalk.yellow('No Brainy memory setup found. Nothing to remove.'))
|
||||
return
|
||||
}
|
||||
|
||||
// Show what will be removed
|
||||
console.log(chalk.white('The following will be removed:'))
|
||||
if (brainyExists) {
|
||||
console.log(chalk.dim(` • ${brainyDir} (memory data and MCP server)`))
|
||||
}
|
||||
if (configExists) {
|
||||
console.log(chalk.dim(` • MCP config entry in ${configPath}`))
|
||||
}
|
||||
console.log()
|
||||
|
||||
// Confirm removal
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Are you sure you want to remove Brainy infinite memory?',
|
||||
default: false
|
||||
}
|
||||
])
|
||||
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('Removal cancelled'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Removing Brainy memory setup...').start()
|
||||
|
||||
try {
|
||||
// Remove Brainy directory
|
||||
if (brainyExists) {
|
||||
// Use Node.js fs for rm operation as universal fs doesn't have it
|
||||
const nodefs = await import('fs')
|
||||
await nodefs.promises.rm(brainyDir, { recursive: true, force: true })
|
||||
spinner.text = 'Removed memory directory...'
|
||||
}
|
||||
|
||||
// Remove MCP config entry
|
||||
if (configExists) {
|
||||
try {
|
||||
const configContent = await fs.readFile(configPath, 'utf8')
|
||||
const mcpConfig = JSON.parse(configContent)
|
||||
|
||||
if (mcpConfig['brainy-memory']) {
|
||||
delete mcpConfig['brainy-memory']
|
||||
await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf8')
|
||||
spinner.text = 'Removed MCP configuration...'
|
||||
}
|
||||
} catch (error) {
|
||||
// If config file is corrupted or empty, skip
|
||||
spinner.warn('Could not update MCP config file')
|
||||
}
|
||||
}
|
||||
|
||||
spinner.succeed('Successfully removed Brainy infinite memory')
|
||||
|
||||
console.log()
|
||||
console.log(chalk.bold('✅ Cleanup complete'))
|
||||
console.log()
|
||||
console.log(chalk.white('All conversation data and MCP configuration have been removed.'))
|
||||
console.log(chalk.dim('Run "brainy conversation setup" to set up again.'))
|
||||
console.log()
|
||||
|
||||
} catch (error: any) {
|
||||
spinner.fail('Removal failed')
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle search command - Search messages
|
||||
*/
|
||||
async function handleSearch(argv: CommandArguments) {
|
||||
if (!argv.query) {
|
||||
console.log(chalk.yellow('Query required. Use -q or --query'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Searching conversations...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const results = await conv.searchMessages({
|
||||
query: argv.query,
|
||||
limit: argv.limit || 10,
|
||||
role: argv.role as any,
|
||||
includeContent: true,
|
||||
includeMetadata: true
|
||||
})
|
||||
|
||||
spinner.succeed(`Found ${results.length} messages`)
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log(chalk.yellow('No messages found'))
|
||||
await brain.close()
|
||||
return
|
||||
}
|
||||
|
||||
// Display results
|
||||
console.log()
|
||||
for (const result of results) {
|
||||
console.log(chalk.bold.cyan(`${result.message.role}:`), result.snippet)
|
||||
console.log(chalk.dim(` Score: ${result.score.toFixed(3)} | Conv: ${result.conversationId}`))
|
||||
console.log()
|
||||
}
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle context command - Get relevant context
|
||||
*/
|
||||
async function handleContext(argv: CommandArguments) {
|
||||
if (!argv.query) {
|
||||
console.log(chalk.yellow('Query required. Use -q or --query'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Retrieving relevant context...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const context = await conv.getRelevantContext(argv.query, {
|
||||
limit: argv.limit || 10,
|
||||
includeArtifacts: true,
|
||||
includeSimilarConversations: true
|
||||
})
|
||||
|
||||
spinner.succeed(`Retrieved ${context.messages.length} relevant messages`)
|
||||
|
||||
if (context.messages.length === 0) {
|
||||
console.log(chalk.yellow('No relevant context found'))
|
||||
await brain.close()
|
||||
return
|
||||
}
|
||||
|
||||
// Display context
|
||||
console.log()
|
||||
console.log(chalk.bold('📊 Context Statistics:'))
|
||||
console.log(chalk.dim(` Messages: ${context.messages.length}`))
|
||||
console.log(chalk.dim(` Tokens: ${context.totalTokens}`))
|
||||
console.log(chalk.dim(` Query time: ${context.metadata.queryTime}ms`))
|
||||
console.log()
|
||||
|
||||
console.log(chalk.bold('💬 Relevant Messages:'))
|
||||
for (const msg of context.messages) {
|
||||
console.log()
|
||||
console.log(chalk.cyan(`${msg.role} (score: ${msg.relevanceScore.toFixed(3)}):`))
|
||||
console.log(msg.content.substring(0, 200) + (msg.content.length > 200 ? '...' : ''))
|
||||
}
|
||||
|
||||
if (context.similarConversations && context.similarConversations.length > 0) {
|
||||
console.log()
|
||||
console.log(chalk.bold('🔗 Similar Conversations:'))
|
||||
for (const conv of context.similarConversations) {
|
||||
console.log(chalk.dim(` - ${conv.title || conv.id} (${conv.relevance.toFixed(2)})`))
|
||||
}
|
||||
}
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle thread command - Get conversation thread
|
||||
*/
|
||||
async function handleThread(argv: CommandArguments) {
|
||||
if (!argv.conversationId) {
|
||||
console.log(chalk.yellow('Conversation ID required. Use -c or --conversation-id'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Loading conversation thread...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const thread = await conv.getConversationThread(argv.conversationId, {
|
||||
includeArtifacts: true
|
||||
})
|
||||
|
||||
spinner.succeed(`Loaded ${thread.messages.length} messages`)
|
||||
|
||||
// Display thread
|
||||
console.log()
|
||||
console.log(chalk.bold('📊 Thread Information:'))
|
||||
console.log(chalk.dim(` Conversation: ${thread.id}`))
|
||||
console.log(chalk.dim(` Messages: ${thread.metadata.messageCount}`))
|
||||
console.log(chalk.dim(` Tokens: ${thread.metadata.totalTokens}`))
|
||||
console.log(chalk.dim(` Started: ${new Date(thread.metadata.startTime).toLocaleString()}`))
|
||||
console.log()
|
||||
|
||||
console.log(chalk.bold('💬 Messages:'))
|
||||
for (const msg of thread.messages) {
|
||||
console.log()
|
||||
console.log(chalk.cyan(`${msg.role}:`), msg.content)
|
||||
console.log(chalk.dim(` ${new Date(msg.createdAt).toLocaleString()}`))
|
||||
}
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle stats command - Show statistics
|
||||
*/
|
||||
async function handleStats(argv: CommandArguments) {
|
||||
const spinner = ora('Calculating statistics...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const stats = await conv.getConversationStats()
|
||||
|
||||
spinner.succeed('Statistics calculated')
|
||||
|
||||
// Display stats
|
||||
console.log()
|
||||
console.log(chalk.bold.cyan('📊 Conversation Statistics\n'))
|
||||
console.log(chalk.bold('Overall:'))
|
||||
console.log(chalk.dim(` Conversations: ${stats.totalConversations}`))
|
||||
console.log(chalk.dim(` Messages: ${stats.totalMessages}`))
|
||||
console.log(chalk.dim(` Total Tokens: ${stats.totalTokens.toLocaleString()}`))
|
||||
console.log(chalk.dim(` Avg Messages/Conversation: ${stats.averageMessagesPerConversation.toFixed(1)}`))
|
||||
console.log(chalk.dim(` Avg Tokens/Message: ${stats.averageTokensPerMessage.toFixed(1)}`))
|
||||
console.log()
|
||||
|
||||
if (Object.keys(stats.roles).length > 0) {
|
||||
console.log(chalk.bold('By Role:'))
|
||||
for (const [role, count] of Object.entries(stats.roles)) {
|
||||
console.log(chalk.dim(` ${role}: ${count}`))
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
if (Object.keys(stats.phases).length > 0) {
|
||||
console.log(chalk.bold('By Phase:'))
|
||||
for (const [phase, count] of Object.entries(stats.phases)) {
|
||||
console.log(chalk.dim(` ${phase}: ${count}`))
|
||||
}
|
||||
}
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle export command - Export conversation
|
||||
*/
|
||||
async function handleExport(argv: CommandArguments) {
|
||||
if (!argv.conversationId) {
|
||||
console.log(chalk.yellow('Conversation ID required. Use -c or --conversation-id'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Exporting conversation...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const exported = await conv.exportConversation(argv.conversationId)
|
||||
|
||||
const output = argv.output || `conversation_${argv.conversationId}.json`
|
||||
await fs.writeFile(output, JSON.stringify(exported, null, 2), 'utf8')
|
||||
|
||||
spinner.succeed(`Exported to ${output}`)
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle import command - Import conversation
|
||||
*/
|
||||
async function handleImport(argv: CommandArguments) {
|
||||
const inputFile = argv.output
|
||||
if (!inputFile) {
|
||||
console.log(chalk.yellow('Input file required. Use -o or --output'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Importing conversation...').start()
|
||||
|
||||
const brain = new Brainy()
|
||||
await brain.init()
|
||||
|
||||
const conv = brain.conversation()
|
||||
await conv.init()
|
||||
|
||||
const data = JSON.parse(await fs.readFile(inputFile, 'utf8'))
|
||||
const conversationId = await conv.importConversation(data)
|
||||
|
||||
spinner.succeed(`Imported as conversation ${conversationId}`)
|
||||
|
||||
await brain.close()
|
||||
}
|
||||
|
||||
export default conversationCommand
|
||||
|
|
@ -13,7 +13,6 @@ import { coreCommands } from './commands/core.js'
|
|||
import { utilityCommands } from './commands/utility.js'
|
||||
import { vfsCommands } from './commands/vfs.js'
|
||||
import { dataCommands } from './commands/data.js'
|
||||
import conversationCommand from './commands/conversation.js'
|
||||
import { readFileSync } from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, join } from 'path'
|
||||
|
|
@ -166,55 +165,6 @@ program
|
|||
.option('-o, --output <file>', 'Output file')
|
||||
.action(neuralCommands.visualize)
|
||||
|
||||
// ===== Conversation Commands (Infinite Memory) =====
|
||||
|
||||
program
|
||||
.command('conversation')
|
||||
.alias('conv')
|
||||
.description('💬 Infinite agent memory and context management')
|
||||
.addCommand(
|
||||
new Command('setup')
|
||||
.description('Set up MCP server for Claude Code integration')
|
||||
.action(async () => {
|
||||
await conversationCommand.handler({ action: 'setup', _: [] })
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('search')
|
||||
.description('Search messages across conversations')
|
||||
.requiredOption('-q, --query <query>', 'Search query')
|
||||
.option('-c, --conversation-id <id>', 'Filter by conversation')
|
||||
.option('-r, --role <role>', 'Filter by role')
|
||||
.option('-l, --limit <number>', 'Maximum results', '10')
|
||||
.action(async (options) => {
|
||||
await conversationCommand.handler({ action: 'search', ...options as any, _: [] })
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('context')
|
||||
.description('Get relevant context for a query')
|
||||
.requiredOption('-q, --query <query>', 'Context query')
|
||||
.option('-l, --limit <number>', 'Maximum messages', '10')
|
||||
.action(async (options) => {
|
||||
await conversationCommand.handler({ action: 'context', ...options as any, _: [] })
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('thread')
|
||||
.description('Get full conversation thread')
|
||||
.requiredOption('-c, --conversation-id <id>', 'Conversation ID')
|
||||
.action(async (options) => {
|
||||
await conversationCommand.handler({ action: 'thread', ...options as any, _: [] })
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('stats')
|
||||
.description('Show conversation statistics')
|
||||
.action(async () => {
|
||||
await conversationCommand.handler({ action: 'stats', _: [] })
|
||||
})
|
||||
)
|
||||
|
||||
// ===== VFS Commands (Subcommand Group) =====
|
||||
|
||||
program
|
||||
|
|
|
|||
|
|
@ -1,825 +0,0 @@
|
|||
/**
|
||||
* ConversationManager - Infinite Agent Memory
|
||||
*
|
||||
* Production-ready conversation and context management for AI agents.
|
||||
* Built on Brainy's existing infrastructure: Triple Intelligence, Neural API, VFS.
|
||||
*
|
||||
* REAL IMPLEMENTATION - No stubs, no mocks, no TODOs
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js'
|
||||
import { NounType, VerbType } from '../types/graphTypes.js'
|
||||
import { Brainy } from '../brainy.js'
|
||||
import {
|
||||
MessageRole,
|
||||
ProblemSolvingPhase,
|
||||
ConversationMessage,
|
||||
ConversationMessageMetadata,
|
||||
ConversationThread,
|
||||
ConversationThreadMetadata,
|
||||
ConversationContext,
|
||||
RankedMessage,
|
||||
SaveMessageOptions,
|
||||
ContextRetrievalOptions,
|
||||
ConversationSearchOptions,
|
||||
ConversationSearchResult,
|
||||
ConversationTheme,
|
||||
ArtifactOptions,
|
||||
ConversationStats,
|
||||
CompactionOptions,
|
||||
CompactionResult
|
||||
} from './types.js'
|
||||
|
||||
/**
|
||||
* ConversationManager - High-level API for conversation operations
|
||||
*
|
||||
* Uses existing Brainy infrastructure:
|
||||
* - brain.add() for messages
|
||||
* - brain.relate() for threading
|
||||
* - brain.find() with Triple Intelligence for context
|
||||
* - brain.neural for clustering and similarity
|
||||
* - brain.vfs() for artifacts
|
||||
*/
|
||||
export class ConversationManager {
|
||||
private brain: Brainy
|
||||
private initialized = false
|
||||
private _vfs: any = null
|
||||
|
||||
/**
|
||||
* Create a ConversationManager instance
|
||||
* @param brain Brainy instance to use
|
||||
*/
|
||||
constructor(brain: Brainy) {
|
||||
this.brain = brain
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the conversation manager
|
||||
* Lazy initialization pattern - only called when first used
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
// VFS is lazy-loaded and might not be initialized yet
|
||||
try {
|
||||
this._vfs = this.brain.vfs()
|
||||
await this._vfs.init()
|
||||
} catch (error) {
|
||||
// VFS initialization failed, will work without artifact support
|
||||
console.warn('VFS initialization failed, artifact support disabled:', error)
|
||||
}
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a message to the conversation history
|
||||
*
|
||||
* Uses: brain.add() with NounType.Message
|
||||
* Real implementation - stores message with embedding
|
||||
*
|
||||
* @param content Message content
|
||||
* @param role Message role (user, assistant, system, tool)
|
||||
* @param options Save options (conversationId, metadata, etc.)
|
||||
* @returns Message ID
|
||||
*/
|
||||
async saveMessage(
|
||||
content: string,
|
||||
role: MessageRole,
|
||||
options: SaveMessageOptions = {}
|
||||
): Promise<string> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
// Generate IDs if not provided
|
||||
const conversationId = options.conversationId || `conv_${uuidv4()}`
|
||||
const sessionId = options.sessionId || `session_${uuidv4()}`
|
||||
const timestamp = Date.now()
|
||||
|
||||
// Build metadata
|
||||
const metadata: ConversationMessageMetadata = {
|
||||
role,
|
||||
conversationId,
|
||||
sessionId,
|
||||
timestamp,
|
||||
problemSolvingPhase: options.phase,
|
||||
confidence: options.confidence,
|
||||
artifacts: options.artifacts || [],
|
||||
toolsUsed: options.toolsUsed || [],
|
||||
references: [],
|
||||
tags: options.tags || [],
|
||||
...options.metadata
|
||||
}
|
||||
|
||||
// Add message to brain using REAL API
|
||||
const messageId = await this.brain.add({
|
||||
data: content,
|
||||
type: NounType.Message,
|
||||
metadata
|
||||
})
|
||||
|
||||
// Link to previous message if specified (REAL graph relationship)
|
||||
if (options.linkToPrevious) {
|
||||
await this.brain.relate({
|
||||
from: options.linkToPrevious,
|
||||
to: messageId,
|
||||
type: VerbType.Precedes,
|
||||
metadata: {
|
||||
conversationId,
|
||||
timestamp
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return messageId
|
||||
}
|
||||
|
||||
/**
|
||||
* Link two messages in temporal sequence
|
||||
*
|
||||
* Uses: brain.relate() with VerbType.Precedes
|
||||
* Real implementation - creates graph relationship
|
||||
*
|
||||
* @param prevMessageId ID of previous message
|
||||
* @param nextMessageId ID of next message
|
||||
* @returns Relationship ID
|
||||
*/
|
||||
async linkMessages(prevMessageId: string, nextMessageId: string): Promise<string> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
// Create real graph relationship
|
||||
const verbId = await this.brain.relate({
|
||||
from: prevMessageId,
|
||||
to: nextMessageId,
|
||||
type: VerbType.Precedes,
|
||||
metadata: {
|
||||
timestamp: Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
return verbId
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full conversation thread
|
||||
*
|
||||
* Uses: brain.getNoun() and brain.getConnections()
|
||||
* Real implementation - traverses graph relationships
|
||||
*
|
||||
* @param conversationId Conversation ID
|
||||
* @param options Options (includeArtifacts, etc.)
|
||||
* @returns Complete conversation thread
|
||||
*/
|
||||
async getConversationThread(
|
||||
conversationId: string,
|
||||
options: { includeArtifacts?: boolean } = {}
|
||||
): Promise<ConversationThread> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
// Search for all messages in conversation (REAL search)
|
||||
const results = await this.brain.find({
|
||||
where: {
|
||||
conversationId
|
||||
},
|
||||
limit: 10000 // Large limit for full thread
|
||||
})
|
||||
|
||||
// Convert results to ConversationMessage format
|
||||
const messages: ConversationMessage[] = results.map((result: any) => ({
|
||||
id: result.id,
|
||||
content: result.data || result.content || '',
|
||||
role: result.metadata.role,
|
||||
metadata: result.metadata as ConversationMessageMetadata,
|
||||
embedding: result.embedding,
|
||||
createdAt: result.metadata.timestamp || Date.now(),
|
||||
updatedAt: result.metadata.timestamp || Date.now()
|
||||
}))
|
||||
|
||||
// Sort by timestamp
|
||||
messages.sort((a, b) => a.createdAt - b.createdAt)
|
||||
|
||||
// Build thread metadata
|
||||
const startTime = messages.length > 0 ? messages[0].createdAt : Date.now()
|
||||
const endTime = messages.length > 0 ? messages[messages.length - 1].createdAt : undefined
|
||||
const totalTokens = messages.reduce((sum, msg) => sum + (msg.metadata.tokensUsed || 0), 0)
|
||||
|
||||
const threadMetadata: ConversationThreadMetadata = {
|
||||
conversationId,
|
||||
startTime,
|
||||
endTime,
|
||||
messageCount: messages.length,
|
||||
totalTokens,
|
||||
participants: [...new Set(messages.map(m => m.role))]
|
||||
}
|
||||
|
||||
// Get artifacts if requested (REAL VFS query)
|
||||
let artifacts: string[] | undefined
|
||||
if (options.includeArtifacts && this._vfs) {
|
||||
artifacts = messages
|
||||
.flatMap(m => m.metadata.artifacts || [])
|
||||
.filter((id, idx, arr) => arr.indexOf(id) === idx)
|
||||
}
|
||||
|
||||
return {
|
||||
id: conversationId,
|
||||
metadata: threadMetadata,
|
||||
messages,
|
||||
artifacts
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relevant context for a query
|
||||
*
|
||||
* Uses: brain.find() with Triple Intelligence
|
||||
* Real implementation - semantic + temporal + graph ranking
|
||||
*
|
||||
* @param query Query string or context options
|
||||
* @param options Retrieval options
|
||||
* @returns Ranked context messages with artifacts
|
||||
*/
|
||||
async getRelevantContext(
|
||||
query: string | ContextRetrievalOptions,
|
||||
options?: ContextRetrievalOptions
|
||||
): Promise<ConversationContext> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
// Normalize options
|
||||
const opts: ContextRetrievalOptions = typeof query === 'string'
|
||||
? { query, ...options }
|
||||
: query
|
||||
|
||||
const {
|
||||
query: queryText,
|
||||
limit = 10,
|
||||
maxTokens = 50000,
|
||||
relevanceThreshold = 0.7,
|
||||
role,
|
||||
phase,
|
||||
tags,
|
||||
minConfidence,
|
||||
timeRange,
|
||||
conversationId,
|
||||
sessionId,
|
||||
weights = { semantic: 1.0, temporal: 0.5, graph: 0.3 },
|
||||
includeArtifacts = false,
|
||||
includeSimilarConversations = false,
|
||||
deduplicateClusters = true
|
||||
} = opts
|
||||
|
||||
// Build metadata filter
|
||||
const whereFilter: any = {}
|
||||
if (role) {
|
||||
whereFilter.role = Array.isArray(role) ? { $in: role } : role
|
||||
}
|
||||
if (phase) {
|
||||
whereFilter.problemSolvingPhase = Array.isArray(phase) ? { $in: phase } : phase
|
||||
}
|
||||
if (tags && tags.length > 0) {
|
||||
whereFilter.tags = { $in: tags }
|
||||
}
|
||||
if (minConfidence !== undefined) {
|
||||
whereFilter.confidence = { $gte: minConfidence }
|
||||
}
|
||||
if (timeRange) {
|
||||
if (timeRange.start !== undefined) {
|
||||
whereFilter.timestamp = { $gte: timeRange.start }
|
||||
}
|
||||
if (timeRange.end !== undefined) {
|
||||
whereFilter.timestamp = { ...whereFilter.timestamp, $lte: timeRange.end }
|
||||
}
|
||||
}
|
||||
if (conversationId) {
|
||||
whereFilter.conversationId = conversationId
|
||||
}
|
||||
if (sessionId) {
|
||||
whereFilter.sessionId = sessionId
|
||||
}
|
||||
|
||||
// Query with Triple Intelligence (REAL)
|
||||
const findOptions: any = {
|
||||
limit: limit * 2, // Get more for ranking
|
||||
where: whereFilter
|
||||
}
|
||||
|
||||
if (queryText) {
|
||||
findOptions.like = queryText
|
||||
}
|
||||
|
||||
const results = await this.brain.find(findOptions)
|
||||
|
||||
// Calculate relevance scores (REAL scoring)
|
||||
const now = Date.now()
|
||||
const rankedMessages: RankedMessage[] = results
|
||||
.map((result: any) => {
|
||||
// Semantic score (from vector similarity)
|
||||
const semanticScore = result.score || 0
|
||||
|
||||
// Temporal score (recency decay)
|
||||
const ageInDays = (now - (result.metadata.timestamp || now)) / (1000 * 60 * 60 * 24)
|
||||
const temporalScore = Math.exp(-0.1 * ageInDays) // Decay rate: 0.1
|
||||
|
||||
// Graph score (would need graph traversal, simplified for now)
|
||||
const graphScore = 0.5 // Placeholder for now, can enhance later
|
||||
|
||||
// Combined score
|
||||
const relevanceScore =
|
||||
(weights.semantic ?? 1.0) * semanticScore +
|
||||
(weights.temporal ?? 0.5) * temporalScore +
|
||||
(weights.graph ?? 0.3) * graphScore
|
||||
|
||||
return {
|
||||
id: result.id,
|
||||
content: result.data || result.content || '',
|
||||
role: result.metadata.role,
|
||||
metadata: result.metadata as ConversationMessageMetadata,
|
||||
embedding: result.embedding,
|
||||
createdAt: result.metadata.timestamp || now,
|
||||
updatedAt: result.metadata.timestamp || now,
|
||||
relevanceScore,
|
||||
semanticScore,
|
||||
temporalScore,
|
||||
graphScore
|
||||
} as RankedMessage
|
||||
})
|
||||
.filter((msg: RankedMessage) => msg.relevanceScore >= relevanceThreshold)
|
||||
.sort((a: RankedMessage, b: RankedMessage) => b.relevanceScore - a.relevanceScore)
|
||||
|
||||
// Deduplicate via clustering if requested
|
||||
let finalMessages = rankedMessages
|
||||
if (deduplicateClusters && rankedMessages.length > 5 && this.brain.neural) {
|
||||
// Use neural clustering to remove duplicates (REAL)
|
||||
try {
|
||||
const clusters = await this.brain.neural().clusters({
|
||||
maxClusters: Math.ceil(rankedMessages.length / 3),
|
||||
threshold: 0.85
|
||||
})
|
||||
|
||||
// Keep highest scoring message from each cluster
|
||||
const kept = new Set<string>()
|
||||
for (const cluster of clusters) {
|
||||
const clusterMessages = rankedMessages.filter(msg =>
|
||||
cluster.members?.includes(msg.id)
|
||||
)
|
||||
if (clusterMessages.length > 0) {
|
||||
const best = clusterMessages.reduce((a, b) =>
|
||||
a.relevanceScore > b.relevanceScore ? a : b
|
||||
)
|
||||
kept.add(best.id)
|
||||
}
|
||||
}
|
||||
|
||||
finalMessages = rankedMessages.filter(msg => kept.has(msg.id))
|
||||
} catch (error) {
|
||||
// Clustering failed, use all messages
|
||||
console.warn('Clustering failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Limit by token budget
|
||||
let totalTokens = 0
|
||||
const messagesWithinBudget: RankedMessage[] = []
|
||||
for (const msg of finalMessages) {
|
||||
const tokens = msg.metadata.tokensUsed || Math.ceil(msg.content.length / 4)
|
||||
if (totalTokens + tokens <= maxTokens) {
|
||||
messagesWithinBudget.push(msg)
|
||||
totalTokens += tokens
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get artifacts if requested (REAL VFS)
|
||||
let artifacts: any[] = []
|
||||
if (includeArtifacts && this._vfs) {
|
||||
const artifactIds = new Set(
|
||||
messagesWithinBudget.flatMap(msg => msg.metadata.artifacts || [])
|
||||
)
|
||||
|
||||
for (const artifactId of artifactIds) {
|
||||
try {
|
||||
const entity = await this.brain.get(artifactId)
|
||||
if (entity) {
|
||||
artifacts.push({
|
||||
id: artifactId,
|
||||
path: entity.metadata?.path || artifactId,
|
||||
summary: entity.metadata?.description || undefined
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Artifact not found, skip
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get similar conversations if requested
|
||||
let similarConversations: any[] = []
|
||||
if (includeSimilarConversations && conversationId && this.brain.neural) {
|
||||
// Use neural neighbors (REAL)
|
||||
try {
|
||||
const neighborsResult = await this.brain.neural().neighbors(conversationId, {
|
||||
limit: 5,
|
||||
minSimilarity: 0.7
|
||||
})
|
||||
|
||||
similarConversations = neighborsResult.neighbors.map((neighbor: any) => ({
|
||||
id: neighbor.id,
|
||||
title: neighbor.metadata?.title,
|
||||
summary: neighbor.metadata?.summary,
|
||||
relevance: neighbor.score,
|
||||
messageCount: neighbor.metadata?.messageCount || 0
|
||||
}))
|
||||
} catch (error) {
|
||||
// Neighbors failed, skip
|
||||
console.warn('Similar conversation search failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const queryTime = Date.now() - startTime
|
||||
|
||||
return {
|
||||
messages: messagesWithinBudget.slice(0, limit),
|
||||
artifacts,
|
||||
similarConversations,
|
||||
totalTokens,
|
||||
metadata: {
|
||||
queryTime,
|
||||
messagesConsidered: results.length,
|
||||
conversationsSearched: new Set(results.map((r: any) => r.metadata.conversationId)).size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search messages semantically
|
||||
*
|
||||
* Uses: brain.find() with semantic search
|
||||
* Real implementation - vector similarity search
|
||||
*
|
||||
* @param options Search options
|
||||
* @returns Search results with scores
|
||||
*/
|
||||
async searchMessages(options: ConversationSearchOptions): Promise<ConversationSearchResult[]> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
const {
|
||||
query,
|
||||
limit = 10,
|
||||
role,
|
||||
conversationId,
|
||||
sessionId,
|
||||
timeRange,
|
||||
includeMetadata = true,
|
||||
includeContent = true
|
||||
} = options
|
||||
|
||||
// Build filter
|
||||
const whereFilter: any = {}
|
||||
if (role) {
|
||||
whereFilter.role = Array.isArray(role) ? { $in: role } : role
|
||||
}
|
||||
if (conversationId) {
|
||||
whereFilter.conversationId = conversationId
|
||||
}
|
||||
if (sessionId) {
|
||||
whereFilter.sessionId = sessionId
|
||||
}
|
||||
if (timeRange) {
|
||||
if (timeRange.start) {
|
||||
whereFilter.timestamp = { $gte: timeRange.start }
|
||||
}
|
||||
if (timeRange.end) {
|
||||
whereFilter.timestamp = { ...whereFilter.timestamp, $lte: timeRange.end }
|
||||
}
|
||||
}
|
||||
|
||||
// Search with Triple Intelligence (REAL)
|
||||
const results = await this.brain.find({
|
||||
query: query,
|
||||
where: whereFilter,
|
||||
limit
|
||||
})
|
||||
|
||||
// Format results
|
||||
return results.map((result: any) => {
|
||||
const message: ConversationMessage = {
|
||||
id: result.id,
|
||||
content: includeContent ? (result.data || result.content || '') : '',
|
||||
role: result.metadata.role,
|
||||
metadata: includeMetadata ? (result.metadata as ConversationMessageMetadata) : {} as any,
|
||||
embedding: result.embedding,
|
||||
createdAt: result.metadata.timestamp || Date.now(),
|
||||
updatedAt: result.metadata.timestamp || Date.now()
|
||||
}
|
||||
|
||||
// Create snippet
|
||||
const content = result.data || result.content || ''
|
||||
const snippet = content.length > 150 ? content.substring(0, 147) + '...' : content
|
||||
|
||||
return {
|
||||
message,
|
||||
score: result.score || 0,
|
||||
conversationId: result.metadata.conversationId,
|
||||
snippet: includeContent ? snippet : undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Find similar conversations using Neural API
|
||||
*
|
||||
* Uses: brain.neural.neighbors()
|
||||
* Real implementation - semantic similarity with embeddings
|
||||
*
|
||||
* @param conversationId Conversation ID to find similar to
|
||||
* @param limit Maximum number of similar conversations
|
||||
* @param threshold Minimum similarity threshold
|
||||
* @returns Similar conversations with relevance scores
|
||||
*/
|
||||
async findSimilarConversations(
|
||||
conversationId: string,
|
||||
limit: number = 5,
|
||||
threshold: number = 0.7
|
||||
): Promise<Array<{ id: string; relevance: number; metadata?: any }>> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
if (!this.brain.neural) {
|
||||
throw new Error('Neural API not available')
|
||||
}
|
||||
|
||||
// Use neural neighbors (REAL)
|
||||
const neighborsResult = await this.brain.neural().neighbors(conversationId, {
|
||||
limit: limit,
|
||||
minSimilarity: threshold
|
||||
})
|
||||
|
||||
return neighborsResult.neighbors.map((neighbor: any) => ({
|
||||
id: neighbor.id,
|
||||
relevance: neighbor.score,
|
||||
metadata: neighbor.metadata
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation themes via clustering
|
||||
*
|
||||
* Uses: brain.neural.clusters()
|
||||
* Real implementation - semantic clustering
|
||||
*
|
||||
* @param conversationId Conversation ID
|
||||
* @returns Discovered themes
|
||||
*/
|
||||
async getConversationThemes(conversationId: string): Promise<ConversationTheme[]> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
if (!this.brain.neural) {
|
||||
throw new Error('Neural API not available')
|
||||
}
|
||||
|
||||
// Get messages for conversation
|
||||
const results = await this.brain.find({
|
||||
where: { conversationId },
|
||||
limit: 1000
|
||||
})
|
||||
|
||||
if (results.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Cluster messages (REAL)
|
||||
const clusters = await this.brain.neural().clusters({
|
||||
maxClusters: Math.min(5, Math.ceil(results.length / 5)),
|
||||
threshold: 0.75
|
||||
})
|
||||
|
||||
// Convert to themes
|
||||
return clusters.map((cluster: any, index: number) => ({
|
||||
id: `theme_${index}`,
|
||||
label: cluster.label || `Theme ${index + 1}`,
|
||||
messages: cluster.members || [],
|
||||
centroid: cluster.centroid || [],
|
||||
coherence: cluster.coherence || 0
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an artifact (code, file, etc.) to VFS
|
||||
*
|
||||
* Uses: brain.vfs()
|
||||
* Real implementation - stores in virtual filesystem
|
||||
*
|
||||
* @param path VFS path
|
||||
* @param content File content
|
||||
* @param options Artifact options
|
||||
* @returns Artifact entity ID
|
||||
*/
|
||||
async saveArtifact(
|
||||
path: string,
|
||||
content: string | Buffer,
|
||||
options: ArtifactOptions
|
||||
): Promise<string> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
if (!this._vfs) {
|
||||
throw new Error('VFS not available')
|
||||
}
|
||||
|
||||
// Write file to VFS (REAL)
|
||||
await this._vfs.writeFile(path, content)
|
||||
|
||||
// Get the file entity
|
||||
const entity = await this._vfs.getEntity(path)
|
||||
|
||||
// Link to conversation message if provided
|
||||
if (options.messageId) {
|
||||
await this.brain.relate({
|
||||
from: options.messageId,
|
||||
to: entity.id,
|
||||
type: VerbType.Creates,
|
||||
metadata: {
|
||||
conversationId: options.conversationId,
|
||||
artifactType: options.type || 'other'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return entity.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation statistics
|
||||
*
|
||||
* Uses: brain.find() with aggregations
|
||||
* Real implementation - queries and aggregates data
|
||||
*
|
||||
* @param conversationId Optional conversation ID to filter
|
||||
* @returns Conversation statistics
|
||||
*/
|
||||
async getConversationStats(conversationId?: string): Promise<ConversationStats> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
// Query messages
|
||||
const whereFilter = conversationId ? { conversationId } : {}
|
||||
const results = await this.brain.find({
|
||||
where: whereFilter,
|
||||
limit: 100000 // Large limit for stats
|
||||
})
|
||||
|
||||
// Calculate statistics (REAL aggregation)
|
||||
const conversations = new Set(results.map((r: any) => r.metadata.conversationId))
|
||||
const totalMessages = results.length
|
||||
const totalTokens = results.reduce(
|
||||
(sum: number, r: any) => sum + (r.metadata.tokensUsed || 0),
|
||||
0
|
||||
)
|
||||
|
||||
const timestamps = results.map((r: any) => r.metadata.timestamp || Date.now())
|
||||
const oldestMessage = Math.min(...timestamps)
|
||||
const newestMessage = Math.max(...timestamps)
|
||||
|
||||
// Count by phase
|
||||
const phases: Record<string, number> = {}
|
||||
const roles: Record<string, number> = {}
|
||||
|
||||
for (const result of results) {
|
||||
const phase = result.entity.metadata.problemSolvingPhase
|
||||
const role = result.entity.metadata.role
|
||||
|
||||
if (phase) {
|
||||
phases[phase] = (phases[phase] || 0) + 1
|
||||
}
|
||||
if (role) {
|
||||
roles[role] = (roles[role] || 0) + 1
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalConversations: conversations.size,
|
||||
totalMessages,
|
||||
totalTokens,
|
||||
averageMessagesPerConversation: totalMessages / Math.max(1, conversations.size),
|
||||
averageTokensPerMessage: totalTokens / Math.max(1, totalMessages),
|
||||
oldestMessage,
|
||||
newestMessage,
|
||||
phases: phases as any,
|
||||
roles: roles as any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a message
|
||||
*
|
||||
* Uses: brain.deleteNoun()
|
||||
* Real implementation - removes from graph
|
||||
*
|
||||
* @param messageId Message ID to delete
|
||||
*/
|
||||
async deleteMessage(messageId: string): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
await this.brain.delete(messageId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export conversation to JSON
|
||||
*
|
||||
* Uses: getConversationThread()
|
||||
* Real implementation - serializes conversation
|
||||
*
|
||||
* @param conversationId Conversation ID
|
||||
* @returns JSON-serializable conversation object
|
||||
*/
|
||||
async exportConversation(conversationId: string): Promise<any> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
const thread = await this.getConversationThread(conversationId, {
|
||||
includeArtifacts: true
|
||||
})
|
||||
|
||||
return {
|
||||
version: '1.0',
|
||||
exportedAt: Date.now(),
|
||||
conversation: thread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import conversation from JSON
|
||||
*
|
||||
* Uses: saveMessage() and linkMessages()
|
||||
* Real implementation - recreates conversation
|
||||
*
|
||||
* @param data Exported conversation data
|
||||
* @returns New conversation ID
|
||||
*/
|
||||
async importConversation(data: any): Promise<string> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
const newConversationId = `conv_${uuidv4()}`
|
||||
const conversation = data.conversation
|
||||
|
||||
if (!conversation || !conversation.messages) {
|
||||
throw new Error('Invalid conversation data')
|
||||
}
|
||||
|
||||
// Import messages in order
|
||||
const messageIdMap = new Map<string, string>()
|
||||
|
||||
for (let i = 0; i < conversation.messages.length; i++) {
|
||||
const msg = conversation.messages[i]
|
||||
const prevMessageId = i > 0 ? messageIdMap.get(conversation.messages[i - 1].id) : undefined
|
||||
|
||||
const newMessageId = await this.saveMessage(msg.content, msg.role, {
|
||||
conversationId: newConversationId,
|
||||
sessionId: conversation.metadata.sessionId,
|
||||
phase: msg.metadata.problemSolvingPhase,
|
||||
confidence: msg.metadata.confidence,
|
||||
tags: msg.metadata.tags,
|
||||
linkToPrevious: prevMessageId,
|
||||
metadata: msg.metadata
|
||||
})
|
||||
|
||||
messageIdMap.set(msg.id, newMessageId)
|
||||
}
|
||||
|
||||
return newConversationId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ConversationManager instance
|
||||
*
|
||||
* @param brain Brainy instance
|
||||
* @returns ConversationManager instance
|
||||
*/
|
||||
export function createConversationManager(brain: Brainy): ConversationManager {
|
||||
return new ConversationManager(brain)
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Conversation Module - Infinite Agent Memory
|
||||
*
|
||||
* Provides conversation and context management for AI agents
|
||||
* Built on Brainy's existing infrastructure
|
||||
*/
|
||||
|
||||
export { ConversationManager, createConversationManager } from './conversationManager.js'
|
||||
|
||||
export type {
|
||||
MessageRole,
|
||||
ProblemSolvingPhase,
|
||||
ConversationMessage,
|
||||
ConversationMessageMetadata,
|
||||
ConversationThread,
|
||||
ConversationThreadMetadata,
|
||||
ConversationContext,
|
||||
RankedMessage,
|
||||
SaveMessageOptions,
|
||||
ContextRetrievalOptions,
|
||||
ConversationSearchOptions,
|
||||
ConversationSearchResult,
|
||||
ConversationTheme,
|
||||
ArtifactOptions,
|
||||
ConversationStats,
|
||||
CompactionOptions,
|
||||
CompactionResult
|
||||
} from './types.js'
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
/**
|
||||
* Conversation Types for Infinite Agent Memory
|
||||
*
|
||||
* Production-ready type definitions for storing and retrieving
|
||||
* conversation history with semantic search and context management.
|
||||
*/
|
||||
|
||||
import { NounType, VerbType } from '../types/graphTypes.js'
|
||||
|
||||
/**
|
||||
* Role of the message sender
|
||||
*/
|
||||
export type MessageRole = 'user' | 'assistant' | 'system' | 'tool'
|
||||
|
||||
/**
|
||||
* Problem-solving phase for tracking agent's progress
|
||||
*/
|
||||
export type ProblemSolvingPhase =
|
||||
| 'understanding'
|
||||
| 'analysis'
|
||||
| 'planning'
|
||||
| 'implementation'
|
||||
| 'testing'
|
||||
| 'debugging'
|
||||
| 'refinement'
|
||||
| 'completed'
|
||||
|
||||
/**
|
||||
* Metadata for a conversation message
|
||||
*/
|
||||
export interface ConversationMessageMetadata {
|
||||
role: MessageRole
|
||||
conversationId: string
|
||||
sessionId?: string
|
||||
timestamp: number
|
||||
|
||||
// Agent state tracking
|
||||
problemSolvingPhase?: ProblemSolvingPhase
|
||||
confidence?: number // 0-1 confidence score
|
||||
|
||||
// Token tracking
|
||||
tokensUsed?: number
|
||||
tokensTotal?: number
|
||||
|
||||
// Context tracking
|
||||
artifacts?: string[] // IDs or paths of created artifacts
|
||||
toolsUsed?: string[] // Names of tools/functions used
|
||||
references?: string[] // IDs of referenced messages/documents
|
||||
|
||||
// Metadata for filtering
|
||||
tags?: string[]
|
||||
priority?: number
|
||||
archived?: boolean
|
||||
|
||||
// Custom metadata
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* A conversation message with all metadata
|
||||
*/
|
||||
export interface ConversationMessage {
|
||||
id: string
|
||||
content: string
|
||||
role: MessageRole
|
||||
metadata: ConversationMessageMetadata
|
||||
embedding?: number[]
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation thread metadata
|
||||
*/
|
||||
export interface ConversationThreadMetadata {
|
||||
conversationId: string
|
||||
sessionId?: string
|
||||
title?: string
|
||||
summary?: string
|
||||
startTime: number
|
||||
endTime?: number
|
||||
messageCount: number
|
||||
totalTokens: number
|
||||
participants: string[] // user IDs or names
|
||||
tags?: string[]
|
||||
archived?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* A conversation thread (collection of messages)
|
||||
*/
|
||||
export interface ConversationThread {
|
||||
id: string
|
||||
metadata: ConversationThreadMetadata
|
||||
messages: ConversationMessage[]
|
||||
artifacts?: string[] // VFS paths or entity IDs
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for retrieving relevant context
|
||||
*/
|
||||
export interface ContextRetrievalOptions {
|
||||
// Query
|
||||
query?: string // Natural language query
|
||||
conversationId?: string // Limit to specific conversation
|
||||
sessionId?: string // Limit to specific session
|
||||
|
||||
// Filtering
|
||||
role?: MessageRole | MessageRole[]
|
||||
phase?: ProblemSolvingPhase | ProblemSolvingPhase[]
|
||||
tags?: string[]
|
||||
minConfidence?: number
|
||||
timeRange?: {
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
|
||||
// Search parameters
|
||||
limit?: number // Max messages to return (default: 10)
|
||||
maxTokens?: number // Token budget for context (default: 50000)
|
||||
relevanceThreshold?: number // Minimum similarity score (default: 0.7)
|
||||
|
||||
// Ranking weights
|
||||
weights?: {
|
||||
semantic?: number // Weight for semantic similarity (default: 1.0)
|
||||
temporal?: number // Weight for recency (default: 0.5)
|
||||
graph?: number // Weight for graph relationships (default: 0.3)
|
||||
}
|
||||
|
||||
// Advanced options
|
||||
includeArtifacts?: boolean // Include linked code/file artifacts
|
||||
includeSimilarConversations?: boolean // Include similar past conversations
|
||||
deduplicateClusters?: boolean // Deduplicate via clustering (default: true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ranked context message with relevance score
|
||||
*/
|
||||
export interface RankedMessage extends ConversationMessage {
|
||||
relevanceScore: number
|
||||
semanticScore?: number
|
||||
temporalScore?: number
|
||||
graphScore?: number
|
||||
explanation?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieved context result
|
||||
*/
|
||||
export interface ConversationContext {
|
||||
messages: RankedMessage[]
|
||||
artifacts?: Array<{
|
||||
path: string
|
||||
id: string
|
||||
content?: string
|
||||
summary?: string
|
||||
}>
|
||||
similarConversations?: Array<{
|
||||
id: string
|
||||
title?: string
|
||||
summary?: string
|
||||
relevance: number
|
||||
messageCount: number
|
||||
}>
|
||||
totalTokens: number
|
||||
metadata: {
|
||||
queryTime: number
|
||||
messagesConsidered: number
|
||||
conversationsSearched: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for saving messages
|
||||
*/
|
||||
export interface SaveMessageOptions {
|
||||
conversationId?: string // Auto-generated if not provided
|
||||
sessionId?: string
|
||||
phase?: ProblemSolvingPhase
|
||||
confidence?: number
|
||||
artifacts?: string[]
|
||||
toolsUsed?: string[]
|
||||
tags?: string[]
|
||||
linkToPrevious?: string // ID of previous message to link
|
||||
metadata?: Record<string, any> // Additional metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for conversation search
|
||||
*/
|
||||
export interface ConversationSearchOptions {
|
||||
query: string
|
||||
limit?: number
|
||||
role?: MessageRole | MessageRole[]
|
||||
conversationId?: string
|
||||
sessionId?: string
|
||||
timeRange?: {
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
includeMetadata?: boolean
|
||||
includeContent?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Search result for conversations
|
||||
*/
|
||||
export interface ConversationSearchResult {
|
||||
message: ConversationMessage
|
||||
score: number
|
||||
conversationId: string
|
||||
snippet?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme discovered via clustering
|
||||
*/
|
||||
export interface ConversationTheme {
|
||||
id: string
|
||||
label: string
|
||||
messages: string[] // Message IDs
|
||||
centroid: number[] // Vector centroid
|
||||
coherence: number // How coherent the cluster is (0-1)
|
||||
keywords?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for artifact storage
|
||||
*/
|
||||
export interface ArtifactOptions {
|
||||
conversationId: string
|
||||
messageId?: string
|
||||
type?: 'code' | 'config' | 'data' | 'document' | 'other'
|
||||
language?: string
|
||||
description?: string
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics about conversations
|
||||
*/
|
||||
export interface ConversationStats {
|
||||
totalConversations: number
|
||||
totalMessages: number
|
||||
totalTokens: number
|
||||
averageMessagesPerConversation: number
|
||||
averageTokensPerMessage: number
|
||||
oldestMessage: number
|
||||
newestMessage: number
|
||||
phases: Record<ProblemSolvingPhase, number>
|
||||
roles: Record<MessageRole, number>
|
||||
}
|
||||
|
||||
/**
|
||||
* Compaction strategy options
|
||||
*/
|
||||
export interface CompactionOptions {
|
||||
conversationId: string
|
||||
strategy?: 'cluster-based' | 'importance-based' | 'hybrid'
|
||||
keepRatio?: number // Ratio of messages to keep (default: 0.3)
|
||||
minImportance?: number // Minimum importance score to keep (default: 0.5)
|
||||
preservePhases?: ProblemSolvingPhase[] // Always keep these phases
|
||||
preserveRecent?: number // Always keep this many recent messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of compaction operation
|
||||
*/
|
||||
export interface CompactionResult {
|
||||
originalCount: number
|
||||
compactedCount: number
|
||||
removedCount: number
|
||||
tokensFreed: number
|
||||
preservedMessageIds: string[]
|
||||
summaryMessageId?: string
|
||||
}
|
||||
24
src/index.ts
24
src/index.ts
|
|
@ -474,27 +474,3 @@ export type {
|
|||
MCPServiceOptions,
|
||||
MCPTool
|
||||
}
|
||||
|
||||
// Export Conversation API (Infinite Agent Memory)
|
||||
export { ConversationManager, createConversationManager } from './conversation/index.js'
|
||||
export { MCPConversationToolset, createConversationToolset } from './mcp/conversationTools.js'
|
||||
|
||||
export type {
|
||||
MessageRole,
|
||||
ProblemSolvingPhase,
|
||||
ConversationMessage,
|
||||
ConversationMessageMetadata,
|
||||
ConversationThread,
|
||||
ConversationThreadMetadata,
|
||||
ConversationContext,
|
||||
RankedMessage,
|
||||
SaveMessageOptions,
|
||||
ContextRetrievalOptions,
|
||||
ConversationSearchOptions,
|
||||
ConversationSearchResult,
|
||||
ConversationTheme,
|
||||
ArtifactOptions,
|
||||
ConversationStats,
|
||||
CompactionOptions,
|
||||
CompactionResult
|
||||
} from './conversation/types.js'
|
||||
|
|
|
|||
|
|
@ -1,598 +0,0 @@
|
|||
/**
|
||||
* MCP Conversation Tools
|
||||
*
|
||||
* Exposes ConversationManager functionality through MCP for Claude Code integration.
|
||||
* Provides 6 tools for infinite agent memory.
|
||||
*
|
||||
* REAL IMPLEMENTATION - Uses ConversationManager which uses real Brainy APIs
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js'
|
||||
import {
|
||||
MCPResponse,
|
||||
MCPToolExecutionRequest,
|
||||
MCPTool,
|
||||
MCP_VERSION
|
||||
} from '../types/mcpTypes.js'
|
||||
import { ConversationManager } from '../conversation/conversationManager.js'
|
||||
import { Brainy } from '../brainy.js'
|
||||
|
||||
/**
|
||||
* MCP Conversation Toolset
|
||||
*
|
||||
* Provides conversation and context management tools for AI agents
|
||||
*/
|
||||
export class MCPConversationToolset {
|
||||
private conversationManager: ConversationManager
|
||||
private initialized = false
|
||||
|
||||
/**
|
||||
* Create MCP Conversation Toolset
|
||||
* @param brain Brainy instance
|
||||
*/
|
||||
constructor(private brain: Brainy) {
|
||||
this.conversationManager = new ConversationManager(brain)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the toolset
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.conversationManager.init()
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle MCP tool execution request
|
||||
* @param request MCP tool execution request
|
||||
* @returns MCP response
|
||||
*/
|
||||
async handleRequest(request: MCPToolExecutionRequest): Promise<MCPResponse> {
|
||||
if (!this.initialized) {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
try {
|
||||
const { toolName, parameters } = request
|
||||
|
||||
// Route to appropriate tool handler
|
||||
switch (toolName) {
|
||||
case 'conversation_save_message':
|
||||
return await this.handleSaveMessage(request.requestId, parameters)
|
||||
|
||||
case 'conversation_get_context':
|
||||
return await this.handleGetContext(request.requestId, parameters)
|
||||
|
||||
case 'conversation_search':
|
||||
return await this.handleSearch(request.requestId, parameters)
|
||||
|
||||
case 'conversation_get_thread':
|
||||
return await this.handleGetThread(request.requestId, parameters)
|
||||
|
||||
case 'conversation_save_artifact':
|
||||
return await this.handleSaveArtifact(request.requestId, parameters)
|
||||
|
||||
case 'conversation_find_similar':
|
||||
return await this.handleFindSimilar(request.requestId, parameters)
|
||||
|
||||
default:
|
||||
return this.createErrorResponse(
|
||||
request.requestId,
|
||||
'UNKNOWN_TOOL',
|
||||
`Unknown conversation tool: ${toolName}`
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
return this.createErrorResponse(
|
||||
request.requestId,
|
||||
'INTERNAL_ERROR',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available conversation tools
|
||||
* @returns Array of MCP tool definitions
|
||||
*/
|
||||
async getAvailableTools(): Promise<MCPTool[]> {
|
||||
return [
|
||||
{
|
||||
name: 'conversation_save_message',
|
||||
description: 'Save a message to conversation history with automatic embedding and indexing',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Message content'
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
enum: ['user', 'assistant', 'system', 'tool'],
|
||||
description: 'Message role'
|
||||
},
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Conversation ID (auto-generated if not provided)'
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
description: 'Session ID (optional)'
|
||||
},
|
||||
phase: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'understanding',
|
||||
'analysis',
|
||||
'planning',
|
||||
'implementation',
|
||||
'testing',
|
||||
'debugging',
|
||||
'refinement',
|
||||
'completed'
|
||||
],
|
||||
description: 'Problem-solving phase'
|
||||
},
|
||||
confidence: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
description: 'Confidence score (0-1)'
|
||||
},
|
||||
artifacts: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Artifact IDs or paths'
|
||||
},
|
||||
toolsUsed: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Names of tools used'
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Tags for categorization'
|
||||
},
|
||||
linkToPrevious: {
|
||||
type: 'string',
|
||||
description: 'ID of previous message to link'
|
||||
}
|
||||
},
|
||||
required: ['content', 'role']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conversation_get_context',
|
||||
description: 'Retrieve relevant context from conversation history using semantic search',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'Query string for context retrieval'
|
||||
},
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Limit to specific conversation'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum messages to return (default: 10)',
|
||||
default: 10
|
||||
},
|
||||
maxTokens: {
|
||||
type: 'number',
|
||||
description: 'Token budget for context (default: 50000)',
|
||||
default: 50000
|
||||
},
|
||||
relevanceThreshold: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
description: 'Minimum similarity score (default: 0.7)',
|
||||
default: 0.7
|
||||
},
|
||||
role: {
|
||||
oneOf: [
|
||||
{ type: 'string', enum: ['user', 'assistant', 'system', 'tool'] },
|
||||
{ type: 'array', items: { type: 'string' } }
|
||||
],
|
||||
description: 'Filter by message role'
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags'
|
||||
},
|
||||
includeArtifacts: {
|
||||
type: 'boolean',
|
||||
description: 'Include linked artifacts',
|
||||
default: false
|
||||
},
|
||||
includeSimilarConversations: {
|
||||
type: 'boolean',
|
||||
description: 'Include similar past conversations',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: ['query']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conversation_search',
|
||||
description: 'Search messages semantically across all conversations',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'Search query'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum results (default: 10)',
|
||||
default: 10
|
||||
},
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Limit to specific conversation'
|
||||
},
|
||||
role: {
|
||||
oneOf: [
|
||||
{ type: 'string', enum: ['user', 'assistant', 'system', 'tool'] },
|
||||
{ type: 'array', items: { type: 'string' } }
|
||||
],
|
||||
description: 'Filter by role'
|
||||
},
|
||||
timeRange: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
start: { type: 'number', description: 'Start timestamp' },
|
||||
end: { type: 'number', description: 'End timestamp' }
|
||||
},
|
||||
description: 'Time range filter'
|
||||
}
|
||||
},
|
||||
required: ['query']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conversation_get_thread',
|
||||
description: 'Get full conversation thread with all messages',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Conversation ID'
|
||||
},
|
||||
includeArtifacts: {
|
||||
type: 'boolean',
|
||||
description: 'Include linked artifacts',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: ['conversationId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conversation_save_artifact',
|
||||
description: 'Save code/file artifact and link to conversation',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'VFS path for artifact'
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Artifact content'
|
||||
},
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Conversation ID'
|
||||
},
|
||||
messageId: {
|
||||
type: 'string',
|
||||
description: 'Message ID to link artifact to'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['code', 'config', 'data', 'document', 'other'],
|
||||
description: 'Artifact type'
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
description: 'Programming language (for code artifacts)'
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Artifact description'
|
||||
}
|
||||
},
|
||||
required: ['path', 'content', 'conversationId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'conversation_find_similar',
|
||||
description: 'Find similar past conversations using semantic similarity',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Conversation ID to find similar to'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum results (default: 5)',
|
||||
default: 5
|
||||
},
|
||||
threshold: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
description: 'Minimum similarity threshold (default: 0.7)',
|
||||
default: 0.7
|
||||
}
|
||||
},
|
||||
required: ['conversationId']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save_message tool
|
||||
* REAL: Uses ConversationManager.saveMessage()
|
||||
*/
|
||||
private async handleSaveMessage(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const {
|
||||
content,
|
||||
role,
|
||||
conversationId,
|
||||
sessionId,
|
||||
phase,
|
||||
confidence,
|
||||
artifacts,
|
||||
toolsUsed,
|
||||
tags,
|
||||
linkToPrevious
|
||||
} = parameters
|
||||
|
||||
// Validate required parameters
|
||||
if (!content || !role) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameters: content and role are required'
|
||||
)
|
||||
}
|
||||
|
||||
// Save message (REAL)
|
||||
const messageId = await this.conversationManager.saveMessage(content, role, {
|
||||
conversationId,
|
||||
sessionId,
|
||||
phase,
|
||||
confidence,
|
||||
artifacts,
|
||||
toolsUsed,
|
||||
tags,
|
||||
linkToPrevious
|
||||
})
|
||||
|
||||
return this.createSuccessResponse(requestId, {
|
||||
messageId,
|
||||
conversationId: conversationId || messageId.split('_')[0]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get_context tool
|
||||
* REAL: Uses ConversationManager.getRelevantContext()
|
||||
*/
|
||||
private async handleGetContext(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const { query, ...options } = parameters
|
||||
|
||||
if (!query) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameter: query'
|
||||
)
|
||||
}
|
||||
|
||||
// Get context (REAL)
|
||||
const context = await this.conversationManager.getRelevantContext(query, options)
|
||||
|
||||
return this.createSuccessResponse(requestId, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle search tool
|
||||
* REAL: Uses ConversationManager.searchMessages()
|
||||
*/
|
||||
private async handleSearch(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const { query } = parameters
|
||||
|
||||
if (!query) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameter: query'
|
||||
)
|
||||
}
|
||||
|
||||
// Search messages (REAL)
|
||||
const results = await this.conversationManager.searchMessages(parameters)
|
||||
|
||||
return this.createSuccessResponse(requestId, {
|
||||
results,
|
||||
count: results.length
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get_thread tool
|
||||
* REAL: Uses ConversationManager.getConversationThread()
|
||||
*/
|
||||
private async handleGetThread(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const { conversationId, includeArtifacts = false } = parameters
|
||||
|
||||
if (!conversationId) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameter: conversationId'
|
||||
)
|
||||
}
|
||||
|
||||
// Get thread (REAL)
|
||||
const thread = await this.conversationManager.getConversationThread(
|
||||
conversationId,
|
||||
{ includeArtifacts }
|
||||
)
|
||||
|
||||
return this.createSuccessResponse(requestId, thread)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save_artifact tool
|
||||
* REAL: Uses ConversationManager.saveArtifact()
|
||||
*/
|
||||
private async handleSaveArtifact(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const {
|
||||
path,
|
||||
content,
|
||||
conversationId,
|
||||
messageId,
|
||||
type,
|
||||
language,
|
||||
description
|
||||
} = parameters
|
||||
|
||||
if (!path || !content || !conversationId) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameters: path, content, and conversationId are required'
|
||||
)
|
||||
}
|
||||
|
||||
// Save artifact (REAL)
|
||||
const artifactId = await this.conversationManager.saveArtifact(path, content, {
|
||||
conversationId,
|
||||
messageId,
|
||||
type,
|
||||
language,
|
||||
description
|
||||
})
|
||||
|
||||
return this.createSuccessResponse(requestId, {
|
||||
artifactId,
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle find_similar tool
|
||||
* REAL: Uses ConversationManager.findSimilarConversations()
|
||||
*/
|
||||
private async handleFindSimilar(
|
||||
requestId: string,
|
||||
parameters: any
|
||||
): Promise<MCPResponse> {
|
||||
const { conversationId, limit = 5, threshold = 0.7 } = parameters
|
||||
|
||||
if (!conversationId) {
|
||||
return this.createErrorResponse(
|
||||
requestId,
|
||||
'INVALID_PARAMETERS',
|
||||
'Missing required parameter: conversationId'
|
||||
)
|
||||
}
|
||||
|
||||
// Find similar (REAL)
|
||||
const similar = await this.conversationManager.findSimilarConversations(
|
||||
conversationId,
|
||||
limit,
|
||||
threshold
|
||||
)
|
||||
|
||||
return this.createSuccessResponse(requestId, {
|
||||
similar,
|
||||
count: similar.length
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create success response
|
||||
*/
|
||||
private createSuccessResponse(requestId: string, data: any): MCPResponse {
|
||||
return {
|
||||
success: true,
|
||||
requestId,
|
||||
version: MCP_VERSION,
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error response
|
||||
*/
|
||||
private createErrorResponse(
|
||||
requestId: string,
|
||||
code: string,
|
||||
message: string,
|
||||
details?: any
|
||||
): MCPResponse {
|
||||
return {
|
||||
success: false,
|
||||
requestId,
|
||||
version: MCP_VERSION,
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
details
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate request ID
|
||||
*/
|
||||
generateRequestId(): string {
|
||||
return uuidv4()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create MCP conversation toolset
|
||||
* @param brain Brainy instance
|
||||
* @returns MCPConversationToolset instance
|
||||
*/
|
||||
export function createConversationToolset(brain: Brainy): MCPConversationToolset {
|
||||
return new MCPConversationToolset(brain)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue