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