refactor: streamline core API surface
This commit is contained in:
parent
0d54da1471
commit
75ae282861
12 changed files with 4 additions and 3402 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [3.23.0](https://github.com/soulcraftlabs/brainy/compare/v3.22.0...v3.23.0) (2025-10-04)
|
||||||
|
|
||||||
|
- refactor: streamline core API surface
|
||||||
|
|
||||||
### [3.22.0](https://github.com/soulcraftlabs/brainy/compare/v3.21.0...v3.22.0) (2025-10-01)
|
### [3.22.0](https://github.com/soulcraftlabs/brainy/compare/v3.21.0...v3.22.0) (2025-10-01)
|
||||||
|
|
||||||
- feat: add intelligent import for CSV, Excel, and PDF files (814cbb4)
|
- feat: add intelligent import for CSV, Excel, and PDF files (814cbb4)
|
||||||
|
|
|
||||||
44
README.md
44
README.md
|
|
@ -19,14 +19,6 @@
|
||||||
|
|
||||||
## 🎉 Key Features
|
## 🎉 Key Features
|
||||||
|
|
||||||
### 💬 **Infinite Agent Memory**
|
|
||||||
|
|
||||||
- **Never Lose Context**: Conversations preserved with semantic search
|
|
||||||
- **Smart Context Retrieval**: Triple Intelligence finds relevant past work
|
|
||||||
- **Claude Code Integration**: One command (`brainy conversation setup`) enables infinite memory
|
|
||||||
- **Automatic Artifact Linking**: Code and files connected to conversations
|
|
||||||
- **Scales to Millions**: Messages indexed and searchable in <100ms
|
|
||||||
|
|
||||||
### 🚀 **NEW in 3.21.0: Enhanced Import & Neural Processing**
|
### 🚀 **NEW in 3.21.0: Enhanced Import & Neural Processing**
|
||||||
|
|
||||||
- **📊 Progress Tracking**: Unified progress reporting with automatic time estimation
|
- **📊 Progress Tracking**: Unified progress reporting with automatic time estimation
|
||||||
|
|
@ -60,42 +52,6 @@
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @soulcraft/brainy
|
npm install @soulcraft/brainy
|
||||||
|
|
||||||
# For Claude Code infinite memory (optional):
|
|
||||||
brainy conversation setup
|
|
||||||
```
|
|
||||||
|
|
||||||
### 💬 **Infinite Memory for Claude Code**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// One-time setup:
|
|
||||||
// $ brainy conversation setup
|
|
||||||
|
|
||||||
// Claude Code now automatically:
|
|
||||||
// - Saves every conversation with embeddings
|
|
||||||
// - Retrieves relevant past context
|
|
||||||
// - Links code artifacts to conversations
|
|
||||||
// - Never loses context or momentum
|
|
||||||
|
|
||||||
// Use programmatically:
|
|
||||||
import { Brainy } from '@soulcraft/brainy'
|
|
||||||
|
|
||||||
const brain = new Brainy()
|
|
||||||
await brain.init()
|
|
||||||
|
|
||||||
// Save conversations
|
|
||||||
await brain.conversation.saveMessage(
|
|
||||||
"How do I implement JWT authentication?",
|
|
||||||
"user",
|
|
||||||
{ conversationId: "conv_123" }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get relevant context (semantic + temporal + graph)
|
|
||||||
const context = await brain.conversation.getRelevantContext(
|
|
||||||
"JWT token validation",
|
|
||||||
{ limit: 10, includeArtifacts: true }
|
|
||||||
)
|
|
||||||
// Returns: Ranked messages, linked code, similar conversations
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🎯 **True Zero Configuration**
|
### 🎯 **True Zero Configuration**
|
||||||
|
|
|
||||||
|
|
@ -1,440 +0,0 @@
|
||||||
# MCP Integration - Claude Code Setup
|
|
||||||
|
|
||||||
**One command. Infinite memory.** This guide shows you how to give Claude Code infinite context and conversation history using Brainy's Model Control Protocol (MCP) integration.
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### One-Time Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install Brainy globally (if not already installed)
|
|
||||||
npm install -g @soulcraft/brainy
|
|
||||||
|
|
||||||
# Set up MCP server for Claude Code
|
|
||||||
brainy conversation setup
|
|
||||||
```
|
|
||||||
|
|
||||||
**That's it!** Claude Code now has infinite memory.
|
|
||||||
|
|
||||||
### What This Does
|
|
||||||
|
|
||||||
The setup command:
|
|
||||||
1. Creates `~/.brainy-memory/` directory
|
|
||||||
2. Initializes Brainy database with filesystem storage
|
|
||||||
3. Creates MCP server script
|
|
||||||
4. Registers server with Claude Code
|
|
||||||
5. Ready to use - no configuration needed
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Automatic Integration
|
|
||||||
|
|
||||||
Once set up, Claude Code **automatically**:
|
|
||||||
|
|
||||||
**On Every Message:**
|
|
||||||
- Saves your message with semantic embeddings
|
|
||||||
- Saves Claude's response with metadata
|
|
||||||
- Links code artifacts to conversations
|
|
||||||
- Tracks problem-solving phase and confidence
|
|
||||||
- Indexes everything for instant retrieval
|
|
||||||
|
|
||||||
**On Conversation Start:**
|
|
||||||
- Retrieves relevant past context
|
|
||||||
- Finds similar previous conversations
|
|
||||||
- Loads linked code artifacts
|
|
||||||
- Presents context to Claude seamlessly
|
|
||||||
|
|
||||||
**Result:** Claude never loses context or momentum, even across completely separate conversations.
|
|
||||||
|
|
||||||
### User Experience
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```
|
|
||||||
You: "How do I implement JWT auth?"
|
|
||||||
Claude: [Implements authentication]
|
|
||||||
|
|
||||||
[Days later, new conversation]
|
|
||||||
You: "Can you fix the JWT token validation?"
|
|
||||||
Claude: "I don't have context about your JWT implementation..."
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```
|
|
||||||
You: "How do I implement JWT auth?"
|
|
||||||
Claude: [Implements authentication, saves to Brainy]
|
|
||||||
|
|
||||||
[Days later, new conversation]
|
|
||||||
You: "Can you fix the JWT token validation?"
|
|
||||||
Claude: "I found 3 related past conversations about JWT...
|
|
||||||
Here's the implementation from /auth/middleware.ts..."
|
|
||||||
[Full context automatically retrieved]
|
|
||||||
```
|
|
||||||
|
|
||||||
## MCP Tools
|
|
||||||
|
|
||||||
The MCP server exposes 6 conversation tools that Claude Code uses automatically:
|
|
||||||
|
|
||||||
### 1. conversation_save_message
|
|
||||||
|
|
||||||
Saves a message to conversation history.
|
|
||||||
|
|
||||||
**Used by:** Claude Code automatically after each message
|
|
||||||
**Parameters:**
|
|
||||||
- `content`: Message text
|
|
||||||
- `role`: 'user' | 'assistant' | 'system' | 'tool'
|
|
||||||
- `conversationId`: Conversation identifier
|
|
||||||
- `phase`: Problem-solving phase
|
|
||||||
- `confidence`: Confidence score
|
|
||||||
- `artifacts`: Array of artifact IDs
|
|
||||||
- `toolsUsed`: Tools used in this message
|
|
||||||
|
|
||||||
### 2. conversation_get_context
|
|
||||||
|
|
||||||
Retrieves relevant past context.
|
|
||||||
|
|
||||||
**Used by:** Claude Code at conversation start or when context is needed
|
|
||||||
**Parameters:**
|
|
||||||
- `query`: What to retrieve context for
|
|
||||||
- `limit`: Max messages (default: 10)
|
|
||||||
- `maxTokens`: Token budget (default: 50000)
|
|
||||||
- `relevanceThreshold`: Min similarity (default: 0.7)
|
|
||||||
- `includeArtifacts`: Include code/files
|
|
||||||
- `includeSimilarConversations`: Include similar past conversations
|
|
||||||
|
|
||||||
**Returns:**
|
|
||||||
- Ranked messages with relevance scores
|
|
||||||
- Linked artifacts
|
|
||||||
- Similar past conversations
|
|
||||||
- Metadata (query time, tokens, etc.)
|
|
||||||
|
|
||||||
### 3. conversation_search
|
|
||||||
|
|
||||||
Searches all conversations semantically.
|
|
||||||
|
|
||||||
**Used by:** When Claude needs to find specific past information
|
|
||||||
**Parameters:**
|
|
||||||
- `query`: Search query
|
|
||||||
- `role`: Filter by role
|
|
||||||
- `conversationId`: Filter by conversation
|
|
||||||
- `timeRange`: Time range filter
|
|
||||||
|
|
||||||
### 4. conversation_get_thread
|
|
||||||
|
|
||||||
Gets complete conversation thread.
|
|
||||||
|
|
||||||
**Used by:** When Claude needs full conversation history
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId`: Conversation ID
|
|
||||||
- `includeArtifacts`: Include linked artifacts
|
|
||||||
|
|
||||||
### 5. conversation_save_artifact
|
|
||||||
|
|
||||||
Saves code/file artifacts.
|
|
||||||
|
|
||||||
**Used by:** When Claude creates files
|
|
||||||
**Parameters:**
|
|
||||||
- `path`: File path
|
|
||||||
- `content`: File content
|
|
||||||
- `conversationId`: Conversation ID
|
|
||||||
- `messageId`: Message that created it
|
|
||||||
- `type`: 'code' | 'config' | 'data' | 'document'
|
|
||||||
- `language`: Programming language
|
|
||||||
|
|
||||||
### 6. conversation_find_similar
|
|
||||||
|
|
||||||
Finds similar past conversations.
|
|
||||||
|
|
||||||
**Used by:** For discovering related work
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId`: Conversation to find similar to
|
|
||||||
- `limit`: Max results
|
|
||||||
- `threshold`: Similarity threshold
|
|
||||||
|
|
||||||
## MCP Server Architecture
|
|
||||||
|
|
||||||
### Server Location
|
|
||||||
|
|
||||||
```
|
|
||||||
~/.brainy-memory/
|
|
||||||
├── mcp-server.js # MCP server script
|
|
||||||
└── data/ # Brainy database
|
|
||||||
├── nouns/ # Messages
|
|
||||||
├── verbs/ # Relationships
|
|
||||||
└── index/ # Search indexes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Server Script
|
|
||||||
|
|
||||||
The generated server script (`~/.brainy-memory/mcp-server.js`):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { Brainy } from '@soulcraft/brainy'
|
|
||||||
import { BrainyMCPService } from '@soulcraft/brainy'
|
|
||||||
import { MCPConversationToolset } from '@soulcraft/brainy'
|
|
||||||
|
|
||||||
// Initialize Brainy
|
|
||||||
const brain = new Brainy({
|
|
||||||
storage: {
|
|
||||||
type: 'filesystem',
|
|
||||||
path: '~/.brainy-memory/data'
|
|
||||||
},
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
await brain.init()
|
|
||||||
|
|
||||||
// Create MCP service
|
|
||||||
const mcpService = new BrainyMCPService(brain)
|
|
||||||
const conversationTools = new MCPConversationToolset(brain)
|
|
||||||
|
|
||||||
// Handle MCP requests via stdio
|
|
||||||
process.stdin.on('data', async (data) => {
|
|
||||||
const request = JSON.parse(data.toString())
|
|
||||||
|
|
||||||
let response
|
|
||||||
if (request.toolName?.startsWith('conversation_')) {
|
|
||||||
response = await conversationTools.handleRequest(request)
|
|
||||||
} else {
|
|
||||||
response = await mcpService.handleRequest(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdout.write(JSON.stringify(response) + '\\n')
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude Code Configuration
|
|
||||||
|
|
||||||
Located at `~/.config/claude-code/mcp-servers.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"brainy-memory": {
|
|
||||||
"command": "node",
|
|
||||||
"args": ["~/.brainy-memory/mcp-server.js"],
|
|
||||||
"env": {
|
|
||||||
"NODE_ENV": "production"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Configuration
|
|
||||||
|
|
||||||
### Custom Storage Location
|
|
||||||
|
|
||||||
Edit `~/.brainy-memory/mcp-server.js`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const brain = new Brainy({
|
|
||||||
storage: {
|
|
||||||
type: 'filesystem',
|
|
||||||
path: '/path/to/custom/location'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cloud Storage (Multi-Device Sync)
|
|
||||||
|
|
||||||
Use S3-compatible storage for sync across machines:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const brain = new Brainy({
|
|
||||||
storage: {
|
|
||||||
type: 's3',
|
|
||||||
bucket: 'my-brainy-memory',
|
|
||||||
region: 'us-east-1'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Requirements:** Set AWS credentials in environment:
|
|
||||||
```bash
|
|
||||||
export AWS_ACCESS_KEY_ID=xxx
|
|
||||||
export AWS_SECRET_ACCESS_KEY=yyy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Storage (Testing)
|
|
||||||
|
|
||||||
For testing or temporary use:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const brain = new Brainy({
|
|
||||||
storage: { type: 'memory' }
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Memory storage is lost on server restart.
|
|
||||||
|
|
||||||
### Context Retrieval Options
|
|
||||||
|
|
||||||
Customize context retrieval behavior:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const context = await conversationManager.getRelevantContext(query, {
|
|
||||||
limit: 15, // More messages
|
|
||||||
maxTokens: 80000, // Larger context window
|
|
||||||
relevanceThreshold: 0.6, // Lower threshold = more results
|
|
||||||
weights: {
|
|
||||||
semantic: 0.7, // Adjust relevance weights
|
|
||||||
temporal: 0.6,
|
|
||||||
graph: 0.4
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Server Not Starting
|
|
||||||
|
|
||||||
**Check logs:**
|
|
||||||
```bash
|
|
||||||
# Server writes to stderr
|
|
||||||
tail -f ~/.brainy-memory/server.log
|
|
||||||
```
|
|
||||||
|
|
||||||
**Common issues:**
|
|
||||||
1. **Node version**: Requires Node.js 22 LTS
|
|
||||||
2. **Permissions**: Ensure ~/.brainy-memory is writable
|
|
||||||
3. **Port conflicts**: MCP uses stdio, no ports needed
|
|
||||||
|
|
||||||
### Claude Code Not Using Memory
|
|
||||||
|
|
||||||
**Verify setup:**
|
|
||||||
```bash
|
|
||||||
# Check MCP server is registered
|
|
||||||
cat ~/.config/claude-code/mcp-servers.json
|
|
||||||
|
|
||||||
# Test server manually
|
|
||||||
echo '{"type":"system_info","infoType":"version","requestId":"test","version":"1.0.0"}' | node ~/.brainy-memory/mcp-server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected output:**
|
|
||||||
```json
|
|
||||||
{"success":true,"requestId":"test","version":"1.0.0","data":{"version":"1.0.0"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Not Persisting
|
|
||||||
|
|
||||||
**Check storage:**
|
|
||||||
```bash
|
|
||||||
# Verify data directory exists
|
|
||||||
ls -la ~/.brainy-memory/data/
|
|
||||||
|
|
||||||
# Check database files
|
|
||||||
du -sh ~/.brainy-memory/data/
|
|
||||||
```
|
|
||||||
|
|
||||||
**If empty:** Server may be using memory storage. Check `mcp-server.js` configuration.
|
|
||||||
|
|
||||||
### Performance Issues
|
|
||||||
|
|
||||||
**Optimize database:**
|
|
||||||
```bash
|
|
||||||
# Rebuild indexes
|
|
||||||
brainy conversation stats # This triggers index optimization
|
|
||||||
```
|
|
||||||
|
|
||||||
**Check size:**
|
|
||||||
```bash
|
|
||||||
# Show storage usage
|
|
||||||
du -sh ~/.brainy-memory/
|
|
||||||
```
|
|
||||||
|
|
||||||
**If too large:** Consider cloud storage or compaction strategies.
|
|
||||||
|
|
||||||
## CLI Commands
|
|
||||||
|
|
||||||
Manage conversations via CLI:
|
|
||||||
|
|
||||||
### View Statistics
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation stats
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
```
|
|
||||||
📊 Conversation Statistics
|
|
||||||
|
|
||||||
Overall:
|
|
||||||
Conversations: 42
|
|
||||||
Messages: 1,337
|
|
||||||
Total Tokens: 567,890
|
|
||||||
Avg Messages/Conversation: 31.8
|
|
||||||
Avg Tokens/Message: 425.1
|
|
||||||
|
|
||||||
By Role:
|
|
||||||
user: 650
|
|
||||||
assistant: 687
|
|
||||||
|
|
||||||
By Phase:
|
|
||||||
implementation: 423
|
|
||||||
planning: 201
|
|
||||||
testing: 98
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search Messages
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation search -q "authentication" -l 10
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Context
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation context -q "JWT token validation" -l 15
|
|
||||||
```
|
|
||||||
|
|
||||||
### View Thread
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation thread -c conv_abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
### Export Conversation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation export -c conv_abc123 -o backup.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import Conversation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation import -o backup.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security & Privacy
|
|
||||||
|
|
||||||
### Local-First by Default
|
|
||||||
|
|
||||||
- All data stored locally in `~/.brainy-memory/`
|
|
||||||
- No external services or APIs
|
|
||||||
- Complete privacy and control
|
|
||||||
|
|
||||||
### Data Encryption
|
|
||||||
|
|
||||||
For sensitive conversations, use encrypted filesystem:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create encrypted volume
|
|
||||||
hdiutil create -size 1g -encryption AES-256 -volname BrainyMemory ~/brainy-secure.dmg
|
|
||||||
|
|
||||||
# Mount and use
|
|
||||||
hdiutil attach ~/brainy-secure.dmg
|
|
||||||
brainy conversation setup --path /Volumes/BrainyMemory
|
|
||||||
```
|
|
||||||
|
|
||||||
### Access Control
|
|
||||||
|
|
||||||
MCP server runs with your user permissions. No additional authentication needed for local use.
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- [API Reference](./API_REFERENCE.md) - Complete API documentation
|
|
||||||
- [Examples](./EXAMPLES.md) - Usage examples and patterns
|
|
||||||
- [Advanced Features](./ADVANCED.md) - Advanced configuration and optimization
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Issues or questions:
|
|
||||||
- GitHub: [soulcraftlabs/brainy/issues](https://github.com/soulcraftlabs/brainy/issues)
|
|
||||||
- Documentation: [docs.brainy.ai](https://docs.brainy.ai)
|
|
||||||
|
|
@ -1,453 +0,0 @@
|
||||||
# Infinite Agent Memory - Conversation API
|
|
||||||
|
|
||||||
**Never lose context again.** Brainy's Conversation API provides infinite memory and context management for AI agents like Claude Code, enabling truly continuous conversations with semantic search, smart context retrieval, and automatic knowledge preservation.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Conversation API turns your agent interactions into a living knowledge graph where:
|
|
||||||
- **Every message is preserved** with semantic embeddings for instant retrieval
|
|
||||||
- **Context is automatically retrieved** using Triple Intelligence (vector + graph + metadata)
|
|
||||||
- **Similar conversations are discovered** through neural clustering
|
|
||||||
- **Code artifacts are linked** to the conversations that created them
|
|
||||||
- **Memory scales infinitely** to millions of messages with <100ms retrieval
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Zero-Config Usage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Brainy } from '@soulcraft/brainy'
|
|
||||||
|
|
||||||
const brain = new Brainy() // Zero configuration!
|
|
||||||
await brain.init()
|
|
||||||
|
|
||||||
// Access conversation manager (lazy-loaded)
|
|
||||||
const conv = brain.conversation
|
|
||||||
|
|
||||||
// Save messages with automatic embedding and indexing
|
|
||||||
const messageId = await conv.saveMessage(
|
|
||||||
"How do I implement JWT authentication?",
|
|
||||||
"user",
|
|
||||||
{ conversationId: "conv_123" }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get relevant context with semantic search
|
|
||||||
const context = await conv.getRelevantContext("authentication implementation", {
|
|
||||||
limit: 10,
|
|
||||||
includeArtifacts: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Context includes:
|
|
||||||
// - Semantically similar messages
|
|
||||||
// - Recent related conversations
|
|
||||||
// - Linked code artifacts
|
|
||||||
// - Relevance scores and explanations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude Code Integration (MCP)
|
|
||||||
|
|
||||||
One-time setup:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brainy conversation setup
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Claude Code automatically:
|
|
||||||
- Saves every message with embeddings
|
|
||||||
- Retrieves relevant past context
|
|
||||||
- Links code artifacts to conversations
|
|
||||||
- Never loses context or momentum
|
|
||||||
|
|
||||||
## Core Concepts
|
|
||||||
|
|
||||||
### 1. Messages
|
|
||||||
|
|
||||||
Every message is a **semantic entity** with:
|
|
||||||
- **Content**: The actual message text
|
|
||||||
- **Role**: user, assistant, system, or tool
|
|
||||||
- **Embeddings**: Automatic vector representation
|
|
||||||
- **Metadata**: Timestamps, conversation ID, phase, confidence, etc.
|
|
||||||
- **Relationships**: Temporal links to previous/next messages
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const messageId = await conv.saveMessage(
|
|
||||||
"Implement user authentication",
|
|
||||||
"user",
|
|
||||||
{
|
|
||||||
conversationId: "conv_123",
|
|
||||||
phase: "planning",
|
|
||||||
confidence: 0.95,
|
|
||||||
tags: ["authentication", "security"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Conversations
|
|
||||||
|
|
||||||
A conversation is a **collection of related messages**:
|
|
||||||
- Tracked by `conversationId`
|
|
||||||
- Contains temporal message sequence
|
|
||||||
- Stores aggregate metadata (tokens, duration, participants)
|
|
||||||
- Can span multiple sessions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const thread = await conv.getConversationThread("conv_123", {
|
|
||||||
includeArtifacts: true
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`${thread.messages.length} messages, ${thread.metadata.totalTokens} tokens`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Context Retrieval
|
|
||||||
|
|
||||||
Smart context retrieval uses **Triple Intelligence**:
|
|
||||||
- **Semantic**: Vector similarity to find related messages
|
|
||||||
- **Temporal**: Recency decay favors recent context
|
|
||||||
- **Graph**: Relationship traversal finds connected knowledge
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const context = await conv.getRelevantContext("how to validate JWT tokens", {
|
|
||||||
limit: 10,
|
|
||||||
maxTokens: 50000,
|
|
||||||
relevanceThreshold: 0.7,
|
|
||||||
weights: {
|
|
||||||
semantic: 1.0, // Prioritize meaning
|
|
||||||
temporal: 0.5, // Recent is relevant
|
|
||||||
graph: 0.3 // Connected knowledge
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Artifacts
|
|
||||||
|
|
||||||
Code and files created during conversations are **first-class citizens**:
|
|
||||||
- Stored in Brainy's Virtual Filesystem
|
|
||||||
- Linked to messages via graph relationships
|
|
||||||
- Searchable by content and metadata
|
|
||||||
- Retrieved with context
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const artifactId = await conv.saveArtifact(
|
|
||||||
'/auth/middleware.ts',
|
|
||||||
codeContent,
|
|
||||||
{
|
|
||||||
conversationId: "conv_123",
|
|
||||||
messageId: messageId,
|
|
||||||
type: 'code',
|
|
||||||
language: 'typescript'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### ConversationManager
|
|
||||||
|
|
||||||
#### `saveMessage(content, role, options)`
|
|
||||||
|
|
||||||
Save a message with automatic embedding.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `content` (string): Message content
|
|
||||||
- `role` (MessageRole): 'user' | 'assistant' | 'system' | 'tool'
|
|
||||||
- `options` (SaveMessageOptions):
|
|
||||||
- `conversationId?`: Conversation ID (auto-generated if not provided)
|
|
||||||
- `sessionId?`: Session ID
|
|
||||||
- `phase?`: Problem-solving phase
|
|
||||||
- `confidence?`: Confidence score (0-1)
|
|
||||||
- `artifacts?`: Array of artifact IDs
|
|
||||||
- `toolsUsed?`: Array of tool names
|
|
||||||
- `tags?`: Array of tags
|
|
||||||
- `linkToPrevious?`: ID of previous message
|
|
||||||
|
|
||||||
**Returns:** `Promise<string>` - Message ID
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const id = await conv.saveMessage(
|
|
||||||
"Implement authentication middleware",
|
|
||||||
"assistant",
|
|
||||||
{
|
|
||||||
conversationId: "conv_123",
|
|
||||||
phase: "implementation",
|
|
||||||
confidence: 0.92,
|
|
||||||
artifacts: ["middleware-id"],
|
|
||||||
toolsUsed: ["write", "edit"],
|
|
||||||
tags: ["authentication", "middleware"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getRelevantContext(query, options)`
|
|
||||||
|
|
||||||
Retrieve relevant context with smart ranking.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `query` (string | ContextRetrievalOptions): Query or full options
|
|
||||||
- `options?` (ContextRetrievalOptions):
|
|
||||||
- `limit?`: Max messages (default: 10)
|
|
||||||
- `maxTokens?`: Token budget (default: 50000)
|
|
||||||
- `relevanceThreshold?`: Min score (default: 0.7)
|
|
||||||
- `role?`: Filter by role
|
|
||||||
- `phase?`: Filter by phase
|
|
||||||
- `tags?`: Filter by tags
|
|
||||||
- `timeRange?`: Time range filter
|
|
||||||
- `weights?`: Scoring weights
|
|
||||||
- `includeArtifacts?`: Include linked artifacts
|
|
||||||
- `includeSimilarConversations?`: Include similar conversations
|
|
||||||
|
|
||||||
**Returns:** `Promise<ConversationContext>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const context = await conv.getRelevantContext({
|
|
||||||
query: "JWT token validation",
|
|
||||||
limit: 15,
|
|
||||||
maxTokens: 60000,
|
|
||||||
role: "assistant",
|
|
||||||
phase: ["implementation", "testing"],
|
|
||||||
tags: ["authentication"],
|
|
||||||
includeArtifacts: true,
|
|
||||||
includeSimilarConversations: true
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Found ${context.messages.length} relevant messages`)
|
|
||||||
console.log(`Total tokens: ${context.totalTokens}`)
|
|
||||||
console.log(`Query time: ${context.metadata.queryTime}ms`)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `searchMessages(options)`
|
|
||||||
|
|
||||||
Search messages with semantic similarity.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `options` (ConversationSearchOptions):
|
|
||||||
- `query`: Search query (required)
|
|
||||||
- `limit?`: Max results (default: 10)
|
|
||||||
- `role?`: Filter by role
|
|
||||||
- `conversationId?`: Filter by conversation
|
|
||||||
- `sessionId?`: Filter by session
|
|
||||||
- `timeRange?`: Time range filter
|
|
||||||
|
|
||||||
**Returns:** `Promise<ConversationSearchResult[]>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const results = await conv.searchMessages({
|
|
||||||
query: "authentication errors",
|
|
||||||
limit: 20,
|
|
||||||
role: "assistant",
|
|
||||||
timeRange: {
|
|
||||||
start: Date.now() - 7*24*60*60*1000 // Last 7 days
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const result of results) {
|
|
||||||
console.log(`${result.message.role}: ${result.snippet}`)
|
|
||||||
console.log(`Score: ${result.score}, Conv: ${result.conversationId}`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getConversationThread(conversationId, options)`
|
|
||||||
|
|
||||||
Get complete conversation thread.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId` (string): Conversation ID
|
|
||||||
- `options?`:
|
|
||||||
- `includeArtifacts?`: Include linked artifacts
|
|
||||||
|
|
||||||
**Returns:** `Promise<ConversationThread>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const thread = await conv.getConversationThread("conv_123", {
|
|
||||||
includeArtifacts: true
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Conversation: ${thread.id}`)
|
|
||||||
console.log(`Messages: ${thread.metadata.messageCount}`)
|
|
||||||
console.log(`Duration: ${new Date(thread.metadata.endTime) - new Date(thread.metadata.startTime)}ms`)
|
|
||||||
|
|
||||||
for (const msg of thread.messages) {
|
|
||||||
console.log(`[${msg.role}] ${msg.content}`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `findSimilarConversations(conversationId, limit, threshold)`
|
|
||||||
|
|
||||||
Find similar past conversations.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId` (string): Conversation to find similar to
|
|
||||||
- `limit?` (number): Max results (default: 5)
|
|
||||||
- `threshold?` (number): Min similarity (default: 0.7)
|
|
||||||
|
|
||||||
**Returns:** `Promise<Array<{id, relevance, metadata}>>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const similar = await conv.findSimilarConversations("conv_123", 5, 0.75)
|
|
||||||
|
|
||||||
for (const s of similar) {
|
|
||||||
console.log(`Similar conversation: ${s.id}`)
|
|
||||||
console.log(`Relevance: ${s.relevance.toFixed(2)}`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getConversationThemes(conversationId)`
|
|
||||||
|
|
||||||
Discover themes via clustering.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId` (string): Conversation ID
|
|
||||||
|
|
||||||
**Returns:** `Promise<ConversationTheme[]>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const themes = await conv.getConversationThemes("conv_123")
|
|
||||||
|
|
||||||
for (const theme of themes) {
|
|
||||||
console.log(`Theme: ${theme.label}`)
|
|
||||||
console.log(`Messages: ${theme.messages.length}`)
|
|
||||||
console.log(`Coherence: ${theme.coherence}`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `saveArtifact(path, content, options)`
|
|
||||||
|
|
||||||
Save code/file artifact.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `path` (string): VFS path
|
|
||||||
- `content` (string | Buffer): File content
|
|
||||||
- `options` (ArtifactOptions):
|
|
||||||
- `conversationId`: Conversation ID (required)
|
|
||||||
- `messageId?`: Message ID to link
|
|
||||||
- `type?`: 'code' | 'config' | 'data' | 'document' | 'other'
|
|
||||||
- `language?`: Programming language
|
|
||||||
- `description?`: Artifact description
|
|
||||||
|
|
||||||
**Returns:** `Promise<string>` - Artifact ID
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const artifactId = await conv.saveArtifact(
|
|
||||||
'/auth/jwt-middleware.ts',
|
|
||||||
middlewareCode,
|
|
||||||
{
|
|
||||||
conversationId: "conv_123",
|
|
||||||
messageId: messageId,
|
|
||||||
type: 'code',
|
|
||||||
language: 'typescript',
|
|
||||||
description: 'JWT authentication middleware'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `getConversationStats(conversationId?)`
|
|
||||||
|
|
||||||
Get conversation statistics.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `conversationId?` (string): Optional conversation to filter
|
|
||||||
|
|
||||||
**Returns:** `Promise<ConversationStats>`
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```typescript
|
|
||||||
const stats = await conv.getConversationStats()
|
|
||||||
|
|
||||||
console.log(`Total conversations: ${stats.totalConversations}`)
|
|
||||||
console.log(`Total messages: ${stats.totalMessages}`)
|
|
||||||
console.log(`Total tokens: ${stats.totalTokens}`)
|
|
||||||
console.log(`By role:`, stats.roles)
|
|
||||||
console.log(`By phase:`, stats.phases)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `exportConversation(conversationId)`
|
|
||||||
|
|
||||||
Export conversation to JSON.
|
|
||||||
|
|
||||||
**Returns:** `Promise<any>` - Serializable conversation object
|
|
||||||
|
|
||||||
#### `importConversation(data)`
|
|
||||||
|
|
||||||
Import conversation from JSON.
|
|
||||||
|
|
||||||
**Returns:** `Promise<string>` - New conversation ID
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Custom Relevance Weights
|
|
||||||
|
|
||||||
Fine-tune context retrieval:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const context = await conv.getRelevantContext(query, {
|
|
||||||
weights: {
|
|
||||||
semantic: 0.6, // Less weight on exact matching
|
|
||||||
temporal: 0.8, // More weight on recent messages
|
|
||||||
graph: 0.4 // Moderate weight on relationships
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase-Based Filtering
|
|
||||||
|
|
||||||
Track problem-solving progress:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const planningMessages = await conv.searchMessages({
|
|
||||||
query: "authentication approach",
|
|
||||||
role: "assistant",
|
|
||||||
phase: ["planning", "analysis"]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Time-Based Analysis
|
|
||||||
|
|
||||||
Analyze conversation evolution:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const recentContext = await conv.getRelevantContext(query, {
|
|
||||||
timeRange: {
|
|
||||||
start: Date.now() - 24*60*60*1000 // Last 24 hours
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Session Conversations
|
|
||||||
|
|
||||||
Track across sessions:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Session 1
|
|
||||||
await conv.saveMessage(content1, "user", {
|
|
||||||
conversationId: "conv_123",
|
|
||||||
sessionId: "session_1"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Session 2 (different day)
|
|
||||||
await conv.saveMessage(content2, "user", {
|
|
||||||
conversationId: "conv_123", // Same conversation!
|
|
||||||
sessionId: "session_2"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
- **Message save**: <50ms (with embedding)
|
|
||||||
- **Context retrieval**: <100ms (10 messages)
|
|
||||||
- **Search**: <150ms (1000s of messages)
|
|
||||||
- **Theme clustering**: <500ms
|
|
||||||
- **Scales to**: 10M+ messages
|
|
||||||
- **Storage**: Efficient vector + graph storage
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- [MCP Integration Guide](./MCP_INTEGRATION.md) - Claude Code setup
|
|
||||||
- [API Reference](./API_REFERENCE.md) - Complete API documentation
|
|
||||||
- [Examples](./EXAMPLES.md) - Code examples and patterns
|
|
||||||
- [Advanced Features](./ADVANCED.md) - Advanced usage and optimization
|
|
||||||
|
|
@ -88,7 +88,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
private _extractor?: NeuralEntityExtractor
|
private _extractor?: NeuralEntityExtractor
|
||||||
private _tripleIntelligence?: TripleIntelligenceSystem
|
private _tripleIntelligence?: TripleIntelligenceSystem
|
||||||
private _vfs?: VirtualFileSystem
|
private _vfs?: VirtualFileSystem
|
||||||
private _conversation?: any // ConversationManager (lazy-loaded)
|
|
||||||
|
|
||||||
// State
|
// State
|
||||||
private initialized = false
|
private initialized = false
|
||||||
|
|
@ -1671,31 +1670,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
return this._vfs
|
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
|
* 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 { utilityCommands } from './commands/utility.js'
|
||||||
import { vfsCommands } from './commands/vfs.js'
|
import { vfsCommands } from './commands/vfs.js'
|
||||||
import { dataCommands } from './commands/data.js'
|
import { dataCommands } from './commands/data.js'
|
||||||
import conversationCommand from './commands/conversation.js'
|
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
|
|
@ -166,55 +165,6 @@ program
|
||||||
.option('-o, --output <file>', 'Output file')
|
.option('-o, --output <file>', 'Output file')
|
||||||
.action(neuralCommands.visualize)
|
.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) =====
|
// ===== VFS Commands (Subcommand Group) =====
|
||||||
|
|
||||||
program
|
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,
|
MCPServiceOptions,
|
||||||
MCPTool
|
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