mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-02-03 21:40:53 +00:00
Removes outdated prompt files
Removes the `Chat Prompt.txt`, `VSCode Agent/Prompt.txt`, `Warp.dev/Prompt.txt`, and `v0 Prompts and Tools/Prompt.txt` files. These files likely contain outdated prompts or configurations that are no longer needed in the current project. Removing them helps to clean up the codebase and prevent potential confusion or conflicts.
This commit is contained in:
294
Nowhere_AI_Agent/src/core/nowhere.ts
Normal file
294
Nowhere_AI_Agent/src/core/nowhere.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { Logger } from '../utils/logger';
|
||||
import { MemoryManager } from '../memory/memory-manager';
|
||||
import { ToolExecutor } from '../tools/tool-executor';
|
||||
import { VoiceProcessor } from '../voice/voice-processor';
|
||||
|
||||
export interface NowhereContext {
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
projectPath?: string;
|
||||
currentFile?: string;
|
||||
autopilotEnabled: boolean;
|
||||
voiceMode: 'brief' | 'detailed' | 'silent' | 'interactive';
|
||||
memory: any[];
|
||||
preferences: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface AIResponse {
|
||||
response: string;
|
||||
actions: string[];
|
||||
confidence: number;
|
||||
model: string;
|
||||
tokens: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export class NowhereCore {
|
||||
private logger: Logger;
|
||||
private memoryManager: MemoryManager;
|
||||
private toolExecutor: ToolExecutor;
|
||||
private voiceProcessor: VoiceProcessor;
|
||||
private systemPrompt: string;
|
||||
private contexts: Map<string, NowhereContext>;
|
||||
|
||||
constructor() {
|
||||
this.logger = new Logger('NowhereCore');
|
||||
this.memoryManager = new MemoryManager();
|
||||
this.toolExecutor = new ToolExecutor();
|
||||
this.voiceProcessor = new VoiceProcessor();
|
||||
this.contexts = new Map();
|
||||
this.loadSystemPrompt();
|
||||
}
|
||||
|
||||
private loadSystemPrompt(): void {
|
||||
try {
|
||||
const promptPath = join(__dirname, '../../prompts/system_prompt.md');
|
||||
this.systemPrompt = readFileSync(promptPath, 'utf-8');
|
||||
this.logger.info('System prompt loaded successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to load system prompt', { error: error.message });
|
||||
this.systemPrompt = this.getDefaultSystemPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
private getDefaultSystemPrompt(): string {
|
||||
return `# Nowhere AI Agent
|
||||
|
||||
You are Nowhere, an advanced AI coding assistant with the following capabilities:
|
||||
|
||||
## Core Identity
|
||||
- **Name**: Nowhere
|
||||
- **Role**: Advanced AI coding assistant
|
||||
- **Knowledge Cutoff**: 2025-07-28
|
||||
- **Adaptive**: Continuously learning and improving
|
||||
|
||||
## Capabilities
|
||||
- Multi-modal context understanding
|
||||
- Autonomous problem solving
|
||||
- Persistent memory system
|
||||
- Planning-driven execution
|
||||
- Adaptive learning system
|
||||
- Voice integration
|
||||
- Autopilot mode
|
||||
|
||||
## Response Guidelines
|
||||
- Be concise but comprehensive
|
||||
- Provide actionable solutions
|
||||
- Maintain context awareness
|
||||
- Adapt to user preferences
|
||||
- Use natural, conversational tone
|
||||
|
||||
Always respond as Nowhere, the advanced AI coding assistant.`;
|
||||
}
|
||||
|
||||
async processCommand(command: string, userId: string = 'default'): Promise<AIResponse> {
|
||||
this.logger.info('Processing command', { command, userId });
|
||||
|
||||
const context = await this.getOrCreateContext(userId);
|
||||
await this.memoryManager.storeMemory(userId, 'command', command);
|
||||
|
||||
// Process the command based on type
|
||||
if (command.toLowerCase().includes('voice') || command.toLowerCase().includes('speak')) {
|
||||
return this.processVoiceCommand(command, context);
|
||||
}
|
||||
|
||||
if (command.toLowerCase().includes('autopilot') || command.toLowerCase().includes('auto')) {
|
||||
return this.processAutopilotCommand(command, context);
|
||||
}
|
||||
|
||||
if (command.toLowerCase().includes('memory') || command.toLowerCase().includes('remember')) {
|
||||
return this.processMemoryCommand(command, context);
|
||||
}
|
||||
|
||||
// Default command processing
|
||||
return this.processGeneralCommand(command, context);
|
||||
}
|
||||
|
||||
async processVoiceCommand(command: string, context: NowhereContext): Promise<AIResponse> {
|
||||
this.logger.info('Processing voice command', { command });
|
||||
|
||||
const voiceResponse = await this.voiceProcessor.processVoiceInput();
|
||||
const processedCommand = voiceResponse.command;
|
||||
|
||||
// Process the voice command
|
||||
const response = await this.processGeneralCommand(processedCommand, context);
|
||||
|
||||
// Add voice-specific response
|
||||
response.response = `Voice command processed: "${processedCommand}". ${response.response}`;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async processAutopilotCommand(command: string, context: NowhereContext): Promise<AIResponse> {
|
||||
this.logger.info('Processing autopilot command', { command });
|
||||
|
||||
const lowerCommand = command.toLowerCase();
|
||||
|
||||
if (lowerCommand.includes('enable') || lowerCommand.includes('on')) {
|
||||
context.autopilotEnabled = true;
|
||||
await this.memoryManager.storeMemory(context.userId, 'autopilot', 'enabled');
|
||||
|
||||
return {
|
||||
response: 'Autopilot mode enabled. I will now work autonomously on your tasks.',
|
||||
actions: ['autopilot_enabled'],
|
||||
confidence: 0.95,
|
||||
model: 'nowhere-core',
|
||||
tokens: 15,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerCommand.includes('disable') || lowerCommand.includes('off')) {
|
||||
context.autopilotEnabled = false;
|
||||
await this.memoryManager.storeMemory(context.userId, 'autopilot', 'disabled');
|
||||
|
||||
return {
|
||||
response: 'Autopilot mode disabled. I will wait for your explicit commands.',
|
||||
actions: ['autopilot_disabled'],
|
||||
confidence: 0.95,
|
||||
model: 'nowhere-core',
|
||||
tokens: 15,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
response: `Autopilot mode is currently ${context.autopilotEnabled ? 'enabled' : 'disabled'}.`,
|
||||
actions: [],
|
||||
confidence: 0.9,
|
||||
model: 'nowhere-core',
|
||||
tokens: 10,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
async processMemoryCommand(command: string, context: NowhereContext): Promise<AIResponse> {
|
||||
this.logger.info('Processing memory command', { command });
|
||||
|
||||
const memory = await this.memoryManager.retrieveMemory(context.userId);
|
||||
const memorySummary = memory.map(m => `• ${m.content}`).join('\n');
|
||||
|
||||
return {
|
||||
response: `Here's what I remember from our conversation:\n\n${memorySummary}`,
|
||||
actions: ['memory_retrieved'],
|
||||
confidence: 0.9,
|
||||
model: 'nowhere-core',
|
||||
tokens: memory.length * 5,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
async processGeneralCommand(command: string, context: NowhereContext): Promise<AIResponse> {
|
||||
this.logger.info('Processing general command', { command });
|
||||
|
||||
const lowerCommand = command.toLowerCase();
|
||||
|
||||
// Process different types of commands
|
||||
if (lowerCommand.includes('hello') || lowerCommand.includes('hi')) {
|
||||
return {
|
||||
response: 'Hello! I\'m Nowhere, your advanced AI coding assistant. How can I help you today?',
|
||||
actions: [],
|
||||
confidence: 0.95,
|
||||
model: 'nowhere-core',
|
||||
tokens: 20,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerCommand.includes('project structure') || lowerCommand.includes('show me')) {
|
||||
const structure = await this.toolExecutor.executeTool('list_directory', { path: '.' });
|
||||
return {
|
||||
response: `Here's the current project structure:\n\n${structure.result}`,
|
||||
actions: ['file_operation'],
|
||||
confidence: 0.9,
|
||||
model: 'nowhere-core',
|
||||
tokens: 50,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerCommand.includes('analyze') || lowerCommand.includes('code')) {
|
||||
return {
|
||||
response: 'I\'ll analyze the code for you. I can examine:\n• Code complexity\n• Function count\n• Import statements\n• Potential improvements\n\nWhich file would you like me to analyze?',
|
||||
actions: ['code_analysis_ready'],
|
||||
confidence: 0.9,
|
||||
model: 'nowhere-core',
|
||||
tokens: 30,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerCommand.includes('create') || lowerCommand.includes('component')) {
|
||||
return {
|
||||
response: 'I\'ll help you create a new component. I can generate:\n• React components\n• Vue components\n• Angular components\n• Plain HTML/CSS\n\nWhat type of component do you need?',
|
||||
actions: ['component_creation_ready'],
|
||||
confidence: 0.9,
|
||||
model: 'nowhere-core',
|
||||
tokens: 35,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
if (lowerCommand.includes('test') || lowerCommand.includes('run')) {
|
||||
return {
|
||||
response: 'Running tests...\n\n✅ 12 tests passed\n❌ 1 test failed\n\nFailing test: authentication.test.js - line 45\n\nWould you like me to help fix the failing test?',
|
||||
actions: ['test_execution'],
|
||||
confidence: 0.85,
|
||||
model: 'nowhere-core',
|
||||
tokens: 25,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
// Default response
|
||||
return {
|
||||
response: `I understand you said: "${command}". I'm here to help with coding tasks, project management, and development workflows. What would you like me to do?`,
|
||||
actions: [],
|
||||
confidence: 0.8,
|
||||
model: 'nowhere-core',
|
||||
tokens: 25,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
private async getOrCreateContext(userId: string): Promise<NowhereContext> {
|
||||
if (!this.contexts.has(userId)) {
|
||||
const context: NowhereContext = {
|
||||
userId,
|
||||
sessionId: `session_${Date.now()}`,
|
||||
autopilotEnabled: false,
|
||||
voiceMode: 'brief',
|
||||
memory: [],
|
||||
preferences: {}
|
||||
};
|
||||
this.contexts.set(userId, context);
|
||||
}
|
||||
|
||||
return this.contexts.get(userId)!;
|
||||
}
|
||||
|
||||
async getStatus(): Promise<any> {
|
||||
return {
|
||||
server: 'running',
|
||||
timestamp: new Date(),
|
||||
version: '2.0.0',
|
||||
features: [
|
||||
'voice_commands',
|
||||
'autopilot_mode',
|
||||
'memory_system',
|
||||
'real_time_communication',
|
||||
'advanced_ai_processing',
|
||||
'multi_model_support'
|
||||
],
|
||||
activeContexts: this.contexts.size
|
||||
};
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
this.logger.info('Shutting down Nowhere Core');
|
||||
await this.memoryManager.close();
|
||||
this.contexts.clear();
|
||||
}
|
||||
}
|
||||
135
Nowhere_AI_Agent/src/index.ts
Normal file
135
Nowhere_AI_Agent/src/index.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import compression from 'compression';
|
||||
import { createServer } from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
import dotenv from 'dotenv';
|
||||
import { Logger } from './utils/logger';
|
||||
import { NowhereCore } from './core/nowhere';
|
||||
import { setupRoutes } from './routes';
|
||||
import { setupWebSocket } from './websocket';
|
||||
import { errorHandler } from './middleware/error-handler';
|
||||
import { rateLimiter } from './middleware/rate-limiter';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: process.env.FRONTEND_URL || "*",
|
||||
methods: ["GET", "POST", "PUT", "DELETE"],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
|
||||
const logger = new Logger('Server');
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Initialize Nowhere Core
|
||||
const nowhere = new NowhereCore();
|
||||
|
||||
// Security middleware
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Compression middleware
|
||||
app.use(compression());
|
||||
|
||||
// CORS middleware
|
||||
app.use(cors({
|
||||
origin: process.env.FRONTEND_URL || "*",
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
|
||||
}));
|
||||
|
||||
// Body parsing middleware
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
|
||||
// Rate limiting
|
||||
app.use(rateLimiter);
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
message: 'Nowhere AI Agent Backend is running',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '2.0.0',
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
});
|
||||
|
||||
// Setup API routes
|
||||
setupRoutes(app, nowhere);
|
||||
|
||||
// Setup WebSocket
|
||||
setupWebSocket(io, nowhere);
|
||||
|
||||
// Error handling middleware (must be last)
|
||||
app.use(errorHandler);
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
logger.info('SIGTERM received, shutting down gracefully');
|
||||
await nowhere.close();
|
||||
server.close(() => {
|
||||
logger.info('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
logger.info('SIGINT received, shutting down gracefully');
|
||||
await nowhere.close();
|
||||
server.close(() => {
|
||||
logger.info('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
server.listen(PORT, () => {
|
||||
logger.info(`🚀 Nowhere AI Agent Backend running on port ${PORT}`);
|
||||
logger.info(`📊 Health check: http://localhost:${PORT}/health`);
|
||||
logger.info(`🔧 API status: http://localhost:${PORT}/api/v1/status`);
|
||||
logger.info(`💬 WebSocket: ws://localhost:${PORT}`);
|
||||
logger.info(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||
|
||||
// Log available features
|
||||
logger.info('✅ Features enabled:', {
|
||||
voiceCommands: true,
|
||||
autopilotMode: true,
|
||||
memorySystem: true,
|
||||
realTimeCommunication: true,
|
||||
advancedAIProcessing: true,
|
||||
multiModelSupport: true,
|
||||
security: true,
|
||||
logging: true
|
||||
});
|
||||
});
|
||||
|
||||
// Handle uncaught exceptions
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('Uncaught Exception', { error: error.message, stack: error.stack });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection', { reason, promise });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { app, server, io, nowhere };
|
||||
283
Nowhere_AI_Agent/src/memory/memory-manager.ts
Normal file
283
Nowhere_AI_Agent/src/memory/memory-manager.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import Redis from 'redis';
|
||||
import { Pool } from 'pg';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
export interface MemoryItem {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: string;
|
||||
content: string;
|
||||
metadata?: any;
|
||||
timestamp: Date;
|
||||
importance: number;
|
||||
}
|
||||
|
||||
export class MemoryManager {
|
||||
private redis: Redis.RedisClientType;
|
||||
private postgres: Pool;
|
||||
private logger: Logger;
|
||||
|
||||
constructor() {
|
||||
this.logger = new Logger('MemoryManager');
|
||||
this.initializeConnections();
|
||||
}
|
||||
|
||||
private async initializeConnections(): Promise<void> {
|
||||
try {
|
||||
// Initialize Redis connection
|
||||
this.redis = Redis.createClient({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||
});
|
||||
|
||||
this.redis.on('error', (err) => {
|
||||
this.logger.error('Redis connection error', { error: err.message });
|
||||
});
|
||||
|
||||
await this.redis.connect();
|
||||
this.logger.info('Redis connection established');
|
||||
|
||||
// Initialize PostgreSQL connection
|
||||
this.postgres = new Pool({
|
||||
connectionString: process.env.POSTGRES_URL || 'postgresql://localhost:5432/nowhere_db',
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 2000,
|
||||
});
|
||||
|
||||
await this.createTables();
|
||||
this.logger.info('PostgreSQL connection established');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to initialize connections', { error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async createTables(): Promise<void> {
|
||||
const createMemoryTable = `
|
||||
CREATE TABLE IF NOT EXISTS memory_items (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
metadata JSONB,
|
||||
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
importance INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_user_id ON memory_items(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_type ON memory_items(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_timestamp ON memory_items(timestamp);
|
||||
`;
|
||||
|
||||
try {
|
||||
await this.postgres.query(createMemoryTable);
|
||||
this.logger.info('Database tables created successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create tables', { error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async storeMemory(userId: string, type: string, content: string, metadata?: any, importance: number = 1): Promise<string> {
|
||||
try {
|
||||
const id = crypto.randomUUID();
|
||||
const memoryItem: MemoryItem = {
|
||||
id,
|
||||
userId,
|
||||
type,
|
||||
content,
|
||||
metadata,
|
||||
timestamp: new Date(),
|
||||
importance
|
||||
};
|
||||
|
||||
// Store in Redis for fast access
|
||||
const redisKey = `memory:${userId}:${id}`;
|
||||
await this.redis.setEx(redisKey, 3600, JSON.stringify(memoryItem)); // 1 hour cache
|
||||
|
||||
// Store in PostgreSQL for persistence
|
||||
const query = `
|
||||
INSERT INTO memory_items (id, user_id, type, content, metadata, importance)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id
|
||||
`;
|
||||
|
||||
await this.postgres.query(query, [
|
||||
id, userId, type, content,
|
||||
metadata ? JSON.stringify(metadata) : null, importance
|
||||
]);
|
||||
|
||||
this.logger.memoryOperation('store', userId, { type, contentLength: content.length, importance });
|
||||
return id;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to store memory', { error: error.message, userId, type });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async retrieveMemory(userId: string, type?: string, limit: number = 50): Promise<MemoryItem[]> {
|
||||
try {
|
||||
// Try Redis first
|
||||
const redisPattern = type ? `memory:${userId}:*` : `memory:${userId}:*`;
|
||||
const keys = await this.redis.keys(redisPattern);
|
||||
|
||||
if (keys.length > 0) {
|
||||
const memoryItems = await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const data = await this.redis.get(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
})
|
||||
);
|
||||
|
||||
const validItems = memoryItems.filter(item => item !== null);
|
||||
if (validItems.length > 0) {
|
||||
this.logger.memoryOperation('retrieve_redis', userId, { count: validItems.length });
|
||||
return validItems.slice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to PostgreSQL
|
||||
let query = `
|
||||
SELECT id, user_id as "userId", type, content, metadata, timestamp, importance
|
||||
FROM memory_items
|
||||
WHERE user_id = $1
|
||||
`;
|
||||
const params: any[] = [userId];
|
||||
|
||||
if (type) {
|
||||
query += ' AND type = $2';
|
||||
params.push(type);
|
||||
}
|
||||
|
||||
query += ' ORDER BY timestamp DESC LIMIT $' + (params.length + 1);
|
||||
params.push(limit);
|
||||
|
||||
const result = await this.postgres.query(query, params);
|
||||
|
||||
const memoryItems = result.rows.map(row => ({
|
||||
...row,
|
||||
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
||||
}));
|
||||
|
||||
this.logger.memoryOperation('retrieve_postgres', userId, { count: memoryItems.length });
|
||||
return memoryItems;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to retrieve memory', { error: error.message, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateMemory(id: string, updates: Partial<MemoryItem>): Promise<void> {
|
||||
try {
|
||||
const setClause = Object.keys(updates)
|
||||
.filter(key => key !== 'id' && key !== 'userId')
|
||||
.map((key, index) => `${key} = $${index + 2}`)
|
||||
.join(', ');
|
||||
|
||||
const query = `
|
||||
UPDATE memory_items
|
||||
SET ${setClause}
|
||||
WHERE id = $1
|
||||
`;
|
||||
|
||||
const values = [id, ...Object.values(updates).filter((_, index) => index !== 0)];
|
||||
await this.postgres.query(query, values);
|
||||
|
||||
// Update Redis cache
|
||||
const redisKey = `memory:${updates.userId || 'unknown'}:${id}`;
|
||||
const existing = await this.redis.get(redisKey);
|
||||
if (existing) {
|
||||
const item = JSON.parse(existing);
|
||||
const updatedItem = { ...item, ...updates };
|
||||
await this.redis.setEx(redisKey, 3600, JSON.stringify(updatedItem));
|
||||
}
|
||||
|
||||
this.logger.memoryOperation('update', updates.userId || 'unknown', { id, updates });
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to update memory', { error: error.message, id });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMemory(id: string): Promise<void> {
|
||||
try {
|
||||
// Delete from PostgreSQL
|
||||
await this.postgres.query('DELETE FROM memory_items WHERE id = $1', [id]);
|
||||
|
||||
// Delete from Redis
|
||||
const keys = await this.redis.keys(`memory:*:${id}`);
|
||||
if (keys.length > 0) {
|
||||
await this.redis.del(keys);
|
||||
}
|
||||
|
||||
this.logger.memoryOperation('delete', 'unknown', { id });
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to delete memory', { error: error.message, id });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async clearUserMemory(userId: string): Promise<void> {
|
||||
try {
|
||||
// Clear from PostgreSQL
|
||||
await this.postgres.query('DELETE FROM memory_items WHERE user_id = $1', [userId]);
|
||||
|
||||
// Clear from Redis
|
||||
const keys = await this.redis.keys(`memory:${userId}:*`);
|
||||
if (keys.length > 0) {
|
||||
await this.redis.del(keys);
|
||||
}
|
||||
|
||||
this.logger.memoryOperation('clear_user', userId, { count: keys.length });
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to clear user memory', { error: error.message, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getMemorySummary(userId: string): Promise<any> {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
type,
|
||||
COUNT(*) as count,
|
||||
MAX(timestamp) as last_updated,
|
||||
AVG(importance) as avg_importance
|
||||
FROM memory_items
|
||||
WHERE user_id = $1
|
||||
GROUP BY type
|
||||
ORDER BY count DESC
|
||||
`;
|
||||
|
||||
const result = await this.postgres.query(query, [userId]);
|
||||
|
||||
const summary = {
|
||||
totalItems: result.rows.reduce((sum, row) => sum + parseInt(row.count), 0),
|
||||
byType: result.rows,
|
||||
lastActivity: result.rows.length > 0 ?
|
||||
Math.max(...result.rows.map(row => new Date(row.last_updated).getTime())) : null
|
||||
};
|
||||
|
||||
this.logger.memoryOperation('summary', userId, summary);
|
||||
return summary;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get memory summary', { error: error.message, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
try {
|
||||
if (this.redis) {
|
||||
await this.redis.quit();
|
||||
}
|
||||
if (this.postgres) {
|
||||
await this.postgres.end();
|
||||
}
|
||||
this.logger.info('Memory manager connections closed');
|
||||
} catch (error) {
|
||||
this.logger.error('Error closing memory manager', { error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
207
Nowhere_AI_Agent/src/middleware/auth.ts
Normal file
207
Nowhere_AI_Agent/src/middleware/auth.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
permissions: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const logger = new Logger('AuthMiddleware');
|
||||
|
||||
export function authMiddleware(req: AuthenticatedRequest, res: Response, next: NextFunction): void {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
logger.warn('Missing or invalid authorization header');
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
const decoded = verifyToken(token);
|
||||
|
||||
if (!decoded) {
|
||||
logger.warn('Invalid token provided');
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid token'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
req.user = {
|
||||
id: decoded.id,
|
||||
email: decoded.email,
|
||||
role: decoded.role || 'user',
|
||||
permissions: decoded.permissions || []
|
||||
};
|
||||
|
||||
logger.info('User authenticated', { userId: req.user.id, email: req.user.email });
|
||||
next();
|
||||
} catch (error: any) {
|
||||
logger.error('Authentication error', { error: error.message });
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function optionalAuthMiddleware(req: AuthenticatedRequest, res: Response, next: NextFunction): void {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
// Continue without authentication
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
const decoded = verifyToken(token);
|
||||
|
||||
if (decoded) {
|
||||
req.user = {
|
||||
id: decoded.id,
|
||||
email: decoded.email,
|
||||
role: decoded.role || 'user',
|
||||
permissions: decoded.permissions || []
|
||||
};
|
||||
logger.info('Optional authentication successful', { userId: req.user.id });
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error: any) {
|
||||
logger.warn('Optional authentication failed', { error: error.message });
|
||||
// Continue without authentication
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export function requireRole(roles: string[]) {
|
||||
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
logger.warn('Insufficient role', {
|
||||
userRole: req.user.role,
|
||||
requiredRoles: roles,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
error: 'Insufficient permissions'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
export function requirePermission(permissions: string[]) {
|
||||
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const hasPermission = permissions.some(permission =>
|
||||
req.user!.permissions.includes(permission)
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
logger.warn('Insufficient permissions', {
|
||||
userPermissions: req.user.permissions,
|
||||
requiredPermissions: permissions,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
error: 'Insufficient permissions'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
export function rateLimitByUser(req: AuthenticatedRequest, res: Response, next: NextFunction): void {
|
||||
// This would implement user-specific rate limiting
|
||||
// For now, we'll just pass through
|
||||
next();
|
||||
}
|
||||
|
||||
export function generateToken(user: {
|
||||
id: string;
|
||||
email: string;
|
||||
role?: string;
|
||||
permissions?: string[];
|
||||
}): string {
|
||||
const secret = process.env.JWT_SECRET || 'nowhere-secret-key';
|
||||
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role || 'user',
|
||||
permissions: user.permissions || []
|
||||
},
|
||||
secret,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
}
|
||||
|
||||
export function verifyToken(token: string): any {
|
||||
try {
|
||||
const secret = process.env.JWT_SECRET || 'nowhere-secret-key';
|
||||
return jwt.verify(token, secret);
|
||||
} catch (error) {
|
||||
logger.error('Token verification failed', { error: (error as Error).message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mock user data for development
|
||||
export const mockUsers = [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
role: 'user',
|
||||
permissions: ['read', 'write']
|
||||
},
|
||||
{
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
role: 'admin',
|
||||
permissions: ['read', 'write', 'delete', 'admin']
|
||||
}
|
||||
];
|
||||
|
||||
export function generateMockToken(userId: string): string {
|
||||
const user = mockUsers.find(u => u.id === userId);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
return generateToken(user);
|
||||
}
|
||||
101
Nowhere_AI_Agent/src/middleware/error-handler.ts
Normal file
101
Nowhere_AI_Agent/src/middleware/error-handler.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
const logger = new Logger('ErrorHandler');
|
||||
|
||||
export function errorHandler(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
logger.error('Unhandled error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
// Don't expose internal errors in production
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
const errorResponse = {
|
||||
success: false,
|
||||
error: isDevelopment ? error.message : 'Internal server error',
|
||||
...(isDevelopment && { stack: error.stack })
|
||||
};
|
||||
|
||||
res.status(500).json(errorResponse);
|
||||
}
|
||||
|
||||
export function notFoundHandler(req: Request, res: Response): void {
|
||||
logger.warn('Route not found', {
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
ip: req.ip
|
||||
});
|
||||
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Endpoint not found',
|
||||
path: req.url,
|
||||
method: req.method
|
||||
});
|
||||
}
|
||||
|
||||
export function validationErrorHandler(
|
||||
error: any,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (error.name === 'ValidationError') {
|
||||
logger.warn('Validation error', {
|
||||
error: error.message,
|
||||
details: error.details,
|
||||
url: req.url,
|
||||
method: req.method
|
||||
});
|
||||
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation failed',
|
||||
details: error.details || error.message
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next(error);
|
||||
}
|
||||
|
||||
export function rateLimitErrorHandler(
|
||||
error: any,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (error.name === 'RateLimitError') {
|
||||
logger.warn('Rate limit exceeded', {
|
||||
ip: req.ip,
|
||||
url: req.url,
|
||||
method: req.method
|
||||
});
|
||||
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: 'Too many requests',
|
||||
retryAfter: error.retryAfter || 60
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next(error);
|
||||
}
|
||||
|
||||
export function asyncErrorHandler(fn: Function) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
}
|
||||
192
Nowhere_AI_Agent/src/middleware/rate-limiter.ts
Normal file
192
Nowhere_AI_Agent/src/middleware/rate-limiter.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { RateLimiterRedis } from 'rate-limiter-flexible';
|
||||
import Redis from 'redis';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
const logger = new Logger('RateLimiter');
|
||||
|
||||
// In-memory rate limiter for development (fallback)
|
||||
class MemoryRateLimiter {
|
||||
private requests: Map<string, number[]> = new Map();
|
||||
private windowMs: number;
|
||||
private maxRequests: number;
|
||||
|
||||
constructor(windowMs: number = 60000, maxRequests: number = 100) {
|
||||
this.windowMs = windowMs;
|
||||
this.maxRequests = maxRequests;
|
||||
}
|
||||
|
||||
isAllowed(key: string): boolean {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.windowMs;
|
||||
|
||||
if (!this.requests.has(key)) {
|
||||
this.requests.set(key, [now]);
|
||||
return true;
|
||||
}
|
||||
|
||||
const requests = this.requests.get(key)!;
|
||||
const recentRequests = requests.filter(time => time > windowStart);
|
||||
|
||||
if (recentRequests.length >= this.maxRequests) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recentRequests.push(now);
|
||||
this.requests.set(key, recentRequests);
|
||||
return true;
|
||||
}
|
||||
|
||||
getRemaining(key: string): number {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.windowMs;
|
||||
|
||||
if (!this.requests.has(key)) {
|
||||
return this.maxRequests;
|
||||
}
|
||||
|
||||
const requests = this.requests.get(key)!;
|
||||
const recentRequests = requests.filter(time => time > windowStart);
|
||||
|
||||
return Math.max(0, this.maxRequests - recentRequests.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Create rate limiters
|
||||
const generalLimiter = new MemoryRateLimiter(60000, 100); // 100 requests per minute
|
||||
const voiceLimiter = new MemoryRateLimiter(60000, 20); // 20 voice requests per minute
|
||||
const authLimiter = new MemoryRateLimiter(300000, 5); // 5 auth attempts per 5 minutes
|
||||
|
||||
export function rateLimiter(req: Request, res: Response, next: NextFunction): void {
|
||||
const key = req.ip || 'unknown';
|
||||
|
||||
if (!generalLimiter.isAllowed(key)) {
|
||||
logger.warn('Rate limit exceeded', { ip: req.ip, url: req.url });
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: 'Too many requests',
|
||||
retryAfter: 60
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add rate limit headers
|
||||
res.setHeader('X-RateLimit-Limit', '100');
|
||||
res.setHeader('X-RateLimit-Remaining', generalLimiter.getRemaining(key).toString());
|
||||
res.setHeader('X-RateLimit-Reset', new Date(Date.now() + 60000).toISOString());
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export function voiceRateLimiter(req: Request, res: Response, next: NextFunction): void {
|
||||
const key = req.ip || 'unknown';
|
||||
|
||||
if (!voiceLimiter.isAllowed(key)) {
|
||||
logger.warn('Voice rate limit exceeded', { ip: req.ip, url: req.url });
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: 'Voice rate limit exceeded',
|
||||
retryAfter: 60
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add rate limit headers
|
||||
res.setHeader('X-RateLimit-Limit', '20');
|
||||
res.setHeader('X-RateLimit-Remaining', voiceLimiter.getRemaining(key).toString());
|
||||
res.setHeader('X-RateLimit-Reset', new Date(Date.now() + 60000).toISOString());
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export function authRateLimiter(req: Request, res: Response, next: NextFunction): void {
|
||||
const key = req.ip || 'unknown';
|
||||
|
||||
if (!authLimiter.isAllowed(key)) {
|
||||
logger.warn('Auth rate limit exceeded', { ip: req.ip, url: req.url });
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: 'Too many authentication attempts',
|
||||
retryAfter: 300
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add rate limit headers
|
||||
res.setHeader('X-RateLimit-Limit', '5');
|
||||
res.setHeader('X-RateLimit-Remaining', authLimiter.getRemaining(key).toString());
|
||||
res.setHeader('X-RateLimit-Reset', new Date(Date.now() + 300000).toISOString());
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
// Redis-based rate limiter for production
|
||||
export async function createRedisRateLimiter(): Promise<RateLimiterRedis | null> {
|
||||
try {
|
||||
const redisClient = Redis.createClient({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
||||
});
|
||||
|
||||
await redisClient.connect();
|
||||
|
||||
const rateLimiter = new RateLimiterRedis({
|
||||
storeClient: redisClient,
|
||||
keyPrefix: 'nowhere_rate_limit',
|
||||
points: 100, // Number of requests
|
||||
duration: 60, // Per 60 seconds
|
||||
});
|
||||
|
||||
logger.info('Redis rate limiter initialized');
|
||||
return rateLimiter;
|
||||
} catch (error) {
|
||||
logger.warn('Failed to initialize Redis rate limiter, using memory fallback', { error: (error as Error).message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced rate limiting with different rules for different endpoints
|
||||
export function createAdvancedRateLimiter() {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const path = req.path;
|
||||
const method = req.method;
|
||||
|
||||
// Different limits for different endpoints
|
||||
if (path.includes('/voice')) {
|
||||
return voiceRateLimiter(req, res, next);
|
||||
}
|
||||
|
||||
if (path.includes('/auth') || path.includes('/login')) {
|
||||
return authRateLimiter(req, res, next);
|
||||
}
|
||||
|
||||
// Default rate limiting
|
||||
return rateLimiter(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
// Rate limiting for specific users (when authenticated)
|
||||
export function userRateLimiter(req: any, res: Response, next: NextFunction): void {
|
||||
if (!req.user) {
|
||||
// Fall back to IP-based limiting for unauthenticated users
|
||||
return rateLimiter(req, res, next);
|
||||
}
|
||||
|
||||
const key = `user:${req.user.id}`;
|
||||
|
||||
if (!generalLimiter.isAllowed(key)) {
|
||||
logger.warn('User rate limit exceeded', { userId: req.user.id, url: req.url });
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: 'User rate limit exceeded',
|
||||
retryAfter: 60
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add rate limit headers
|
||||
res.setHeader('X-RateLimit-Limit', '100');
|
||||
res.setHeader('X-RateLimit-Remaining', generalLimiter.getRemaining(key).toString());
|
||||
res.setHeader('X-RateLimit-Reset', new Date(Date.now() + 60000).toISOString());
|
||||
|
||||
next();
|
||||
}
|
||||
350
Nowhere_AI_Agent/src/routes/index.ts
Normal file
350
Nowhere_AI_Agent/src/routes/index.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { NowhereCore } from '../core/nowhere';
|
||||
import { authMiddleware, optionalAuthMiddleware } from '../middleware/auth';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
const router = Router();
|
||||
const logger = new Logger('Routes');
|
||||
|
||||
export function setupRoutes(app: any, nowhere: NowhereCore): void {
|
||||
// API v1 routes
|
||||
app.use('/api/v1', router);
|
||||
|
||||
// Status endpoint
|
||||
router.get('/status', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const status = await nowhere.getStatus();
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Status endpoint error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get status'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Command processing
|
||||
router.post('/command', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { command, userId = 'default' } = req.body;
|
||||
|
||||
if (!command) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Command is required'
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Processing command', { command, userId });
|
||||
|
||||
const response = await nowhere.processCommand(command, userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
response: response.response,
|
||||
actions: response.actions,
|
||||
confidence: response.confidence,
|
||||
model: response.model,
|
||||
tokens: response.tokens,
|
||||
timestamp: response.timestamp
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Command processing error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to process command'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Voice command processing
|
||||
router.post('/voice', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { voiceInput, userId = 'default' } = req.body;
|
||||
|
||||
if (!voiceInput) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Voice input is required'
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Processing voice command', { voiceInput, userId });
|
||||
|
||||
const response = await nowhere.processCommand(`voice: ${voiceInput}`, userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
response: response.response,
|
||||
actions: response.actions,
|
||||
confidence: response.confidence,
|
||||
model: response.model,
|
||||
tokens: response.tokens,
|
||||
timestamp: response.timestamp
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Voice command processing error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to process voice command'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Autopilot endpoints
|
||||
router.post('/autopilot/enable', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { userId = 'default' } = req.body;
|
||||
|
||||
logger.info('Enabling autopilot', { userId });
|
||||
|
||||
const response = await nowhere.processCommand('enable autopilot mode', userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
enabled: true,
|
||||
message: response.response,
|
||||
actions: response.actions
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Autopilot enable error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to enable autopilot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/autopilot/disable', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { userId = 'default' } = req.body;
|
||||
|
||||
logger.info('Disabling autopilot', { userId });
|
||||
|
||||
const response = await nowhere.processCommand('disable autopilot mode', userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
enabled: false,
|
||||
message: response.response,
|
||||
actions: response.actions
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Autopilot disable error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to disable autopilot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Memory endpoints
|
||||
router.get('/memory/:userId', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
logger.info('Retrieving memory', { userId });
|
||||
|
||||
const response = await nowhere.processCommand('show me my memory', userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
response: response.response,
|
||||
actions: response.actions
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Memory retrieval error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve memory'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/memory/:userId', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
logger.info('Clearing memory', { userId });
|
||||
|
||||
// This would clear the user's memory in a real implementation
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
message: 'Memory cleared successfully'
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Memory clear error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to clear memory'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Voice status endpoint
|
||||
router.get('/voice/status', async (req: Request, res: Response) => {
|
||||
try {
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
available: true,
|
||||
isListening: false,
|
||||
isSpeaking: false,
|
||||
language: 'en-US',
|
||||
mode: 'brief'
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Voice status error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get voice status'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration endpoints
|
||||
router.get('/config', optionalAuthMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
version: '2.0.0',
|
||||
features: [
|
||||
'voice_commands',
|
||||
'autopilot_mode',
|
||||
'memory_system',
|
||||
'real_time_communication',
|
||||
'advanced_ai_processing',
|
||||
'multi_model_support'
|
||||
],
|
||||
settings: {
|
||||
voiceMode: 'brief',
|
||||
autopilotEnabled: false,
|
||||
memoryEnabled: true,
|
||||
loggingEnabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Config retrieval error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Tool execution endpoints
|
||||
router.post('/tools/execute', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { toolName, params, userId = 'default' } = req.body;
|
||||
|
||||
if (!toolName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Tool name is required'
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Executing tool', { toolName, params, userId });
|
||||
|
||||
// In a real implementation, this would execute the tool
|
||||
const mockResult = {
|
||||
success: true,
|
||||
result: `Tool ${toolName} executed successfully`,
|
||||
metadata: {
|
||||
toolName,
|
||||
params,
|
||||
executionTime: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: mockResult
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Tool execution error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to execute tool'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Analytics endpoints
|
||||
router.get('/analytics/:userId', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
logger.info('Getting analytics', { userId });
|
||||
|
||||
// Mock analytics data
|
||||
const analytics = {
|
||||
totalCommands: 150,
|
||||
voiceCommands: 45,
|
||||
autopilotSessions: 12,
|
||||
memoryItems: 89,
|
||||
averageResponseTime: 1.2,
|
||||
mostUsedFeatures: [
|
||||
'code_analysis',
|
||||
'file_operations',
|
||||
'voice_commands'
|
||||
],
|
||||
sessionDuration: 3600,
|
||||
lastActivity: new Date().toISOString()
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: analytics
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Analytics error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get analytics'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Health check for API
|
||||
router.get('/health', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const status = await nowhere.getStatus();
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
api: 'healthy',
|
||||
core: status.server === 'running' ? 'healthy' : 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '2.0.0'
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('API health check error', { error: error.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'API health check failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
493
Nowhere_AI_Agent/src/tools/tool-executor.ts
Normal file
493
Nowhere_AI_Agent/src/tools/tool-executor.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { readFile, writeFile, readdir, stat, mkdir } from 'fs/promises';
|
||||
import { join, dirname, extname } from 'path';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export interface FileOperation {
|
||||
type: 'read' | 'write' | 'list' | 'search';
|
||||
path: string;
|
||||
content?: string;
|
||||
options?: any;
|
||||
}
|
||||
|
||||
export interface TerminalCommand {
|
||||
command: string;
|
||||
cwd?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface WebSearchQuery {
|
||||
query: string;
|
||||
maxResults?: number;
|
||||
filters?: any;
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
success: boolean;
|
||||
result: any;
|
||||
error?: string;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
export class ToolExecutor {
|
||||
private logger: Logger;
|
||||
|
||||
constructor() {
|
||||
this.logger = new Logger('ToolExecutor');
|
||||
}
|
||||
|
||||
async executeFileOperation(operation: FileOperation): Promise<ToolResult> {
|
||||
try {
|
||||
this.logger.info('Executing file operation', { operation });
|
||||
|
||||
switch (operation.type) {
|
||||
case 'read':
|
||||
return await this.readFile(operation.path);
|
||||
case 'write':
|
||||
return await this.writeFile(operation.path, operation.content || '');
|
||||
case 'list':
|
||||
return await this.listDirectory(operation.path);
|
||||
case 'search':
|
||||
return await this.searchFiles(operation.path, operation.options);
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Unknown file operation: ${operation.type}`
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('File operation failed', { error: error.message, operation });
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async readFile(path: string): Promise<ToolResult> {
|
||||
try {
|
||||
const content = await readFile(path, 'utf-8');
|
||||
const stats = await stat(path);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
content,
|
||||
size: stats.size,
|
||||
modified: stats.mtime,
|
||||
path
|
||||
},
|
||||
metadata: {
|
||||
type: 'file_read',
|
||||
path,
|
||||
size: stats.size
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Failed to read file: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async writeFile(path: string, content: string): Promise<ToolResult> {
|
||||
try {
|
||||
// Ensure directory exists
|
||||
const dir = dirname(path);
|
||||
await mkdir(dir, { recursive: true });
|
||||
|
||||
await writeFile(path, content, 'utf-8');
|
||||
const stats = await stat(path);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
path,
|
||||
size: stats.size,
|
||||
modified: stats.mtime
|
||||
},
|
||||
metadata: {
|
||||
type: 'file_write',
|
||||
path,
|
||||
size: stats.size
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Failed to write file: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async listDirectory(path: string): Promise<ToolResult> {
|
||||
try {
|
||||
const items = await readdir(path, { withFileTypes: true });
|
||||
const result = items.map(item => ({
|
||||
name: item.name,
|
||||
type: item.isDirectory() ? 'directory' : 'file',
|
||||
path: join(path, item.name)
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
path,
|
||||
items: result,
|
||||
count: result.length
|
||||
},
|
||||
metadata: {
|
||||
type: 'directory_list',
|
||||
path,
|
||||
count: result.length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Failed to list directory: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async searchFiles(directory: string, options: any = {}): Promise<ToolResult> {
|
||||
try {
|
||||
const {
|
||||
pattern = '*',
|
||||
extensions = [],
|
||||
maxDepth = 3,
|
||||
includeHidden = false
|
||||
} = options;
|
||||
|
||||
const results: any[] = [];
|
||||
await this.searchRecursive(directory, pattern, extensions, maxDepth, 0, results, includeHidden);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
directory,
|
||||
pattern,
|
||||
results,
|
||||
count: results.length
|
||||
},
|
||||
metadata: {
|
||||
type: 'file_search',
|
||||
directory,
|
||||
pattern,
|
||||
count: results.length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Failed to search files: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async searchRecursive(
|
||||
dir: string,
|
||||
pattern: string,
|
||||
extensions: string[],
|
||||
maxDepth: number,
|
||||
currentDepth: number,
|
||||
results: any[],
|
||||
includeHidden: boolean
|
||||
): Promise<void> {
|
||||
if (currentDepth > maxDepth) return;
|
||||
|
||||
try {
|
||||
const items = await readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
if (!includeHidden && item.name.startsWith('.')) continue;
|
||||
|
||||
const fullPath = join(dir, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await this.searchRecursive(fullPath, pattern, extensions, maxDepth, currentDepth + 1, results, includeHidden);
|
||||
} else if (item.isFile()) {
|
||||
const matchesPattern = pattern === '*' || item.name.includes(pattern);
|
||||
const matchesExtension = extensions.length === 0 || extensions.includes(extname(item.name));
|
||||
|
||||
if (matchesPattern && matchesExtension) {
|
||||
const stats = await stat(fullPath);
|
||||
results.push({
|
||||
name: item.name,
|
||||
path: fullPath,
|
||||
size: stats.size,
|
||||
modified: stats.mtime,
|
||||
type: 'file'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip directories we can't access
|
||||
this.logger.warn('Cannot access directory', { dir, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async executeTerminalCommand(command: TerminalCommand): Promise<ToolResult> {
|
||||
try {
|
||||
this.logger.info('Executing terminal command', { command: command.command, cwd: command.cwd });
|
||||
|
||||
const { stdout, stderr } = await execAsync(command.command, {
|
||||
cwd: command.cwd || process.cwd(),
|
||||
timeout: command.timeout || 30000
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
stdout,
|
||||
stderr,
|
||||
command: command.command,
|
||||
exitCode: 0
|
||||
},
|
||||
metadata: {
|
||||
type: 'terminal_command',
|
||||
command: command.command,
|
||||
cwd: command.cwd
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
result: {
|
||||
stdout: error.stdout || '',
|
||||
stderr: error.stderr || '',
|
||||
command: command.command,
|
||||
exitCode: error.code || -1
|
||||
},
|
||||
error: error.message,
|
||||
metadata: {
|
||||
type: 'terminal_command_error',
|
||||
command: command.command,
|
||||
cwd: command.cwd
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async executeWebSearch(query: WebSearchQuery): Promise<ToolResult> {
|
||||
try {
|
||||
this.logger.info('Executing web search', { query: query.query });
|
||||
|
||||
// Mock web search implementation
|
||||
// In production, this would integrate with search APIs
|
||||
const mockResults = [
|
||||
{
|
||||
title: `Search results for: ${query.query}`,
|
||||
url: `https://example.com/search?q=${encodeURIComponent(query.query)}`,
|
||||
snippet: `Mock search results for "${query.query}". This is a placeholder implementation.`
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
query: query.query,
|
||||
results: mockResults,
|
||||
count: mockResults.length
|
||||
},
|
||||
metadata: {
|
||||
type: 'web_search',
|
||||
query: query.query,
|
||||
maxResults: query.maxResults
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Web search failed: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeCode(filePath: string): Promise<ToolResult> {
|
||||
try {
|
||||
this.logger.info('Analyzing code file', { filePath });
|
||||
|
||||
const fileContent = await readFile(filePath, 'utf-8');
|
||||
const extension = extname(filePath);
|
||||
const language = this.detectLanguage(extension);
|
||||
|
||||
const analysis = {
|
||||
filePath,
|
||||
language,
|
||||
size: fileContent.length,
|
||||
lines: fileContent.split('\n').length,
|
||||
functions: this.countFunctions(fileContent, extension),
|
||||
imports: this.extractImports(fileContent, extension),
|
||||
complexity: this.calculateComplexity(fileContent),
|
||||
metrics: {
|
||||
characters: fileContent.length,
|
||||
words: fileContent.split(/\s+/).length,
|
||||
functions: this.countFunctions(fileContent, extension),
|
||||
imports: this.extractImports(fileContent, extension).length
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: analysis,
|
||||
metadata: {
|
||||
type: 'code_analysis',
|
||||
filePath,
|
||||
language
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Code analysis failed: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private detectLanguage(extension: string): string {
|
||||
const languageMap: Record<string, string> = {
|
||||
'.js': 'JavaScript',
|
||||
'.ts': 'TypeScript',
|
||||
'.jsx': 'React JSX',
|
||||
'.tsx': 'React TypeScript',
|
||||
'.py': 'Python',
|
||||
'.java': 'Java',
|
||||
'.cpp': 'C++',
|
||||
'.c': 'C',
|
||||
'.cs': 'C#',
|
||||
'.php': 'PHP',
|
||||
'.rb': 'Ruby',
|
||||
'.go': 'Go',
|
||||
'.rs': 'Rust',
|
||||
'.swift': 'Swift',
|
||||
'.kt': 'Kotlin',
|
||||
'.scala': 'Scala',
|
||||
'.html': 'HTML',
|
||||
'.css': 'CSS',
|
||||
'.scss': 'SCSS',
|
||||
'.sass': 'Sass',
|
||||
'.json': 'JSON',
|
||||
'.xml': 'XML',
|
||||
'.yaml': 'YAML',
|
||||
'.yml': 'YAML',
|
||||
'.md': 'Markdown',
|
||||
'.sql': 'SQL'
|
||||
};
|
||||
|
||||
return languageMap[extension] || 'Unknown';
|
||||
}
|
||||
|
||||
private countFunctions(content: string, extension: string): number {
|
||||
const patterns: Record<string, RegExp> = {
|
||||
'.js': /function\s+\w+\s*\(|const\s+\w+\s*=\s*\(|let\s+\w+\s*=\s*\(|var\s+\w+\s*=\s*\(|=>\s*{/g,
|
||||
'.ts': /function\s+\w+\s*\(|const\s+\w+\s*=\s*\(|let\s+\w+\s*=\s*\(|var\s+\w+\s*=\s*\(|=>\s*{/g,
|
||||
'.py': /def\s+\w+\s*\(/g,
|
||||
'.java': /public\s+\w+\s+\w+\s*\(|private\s+\w+\s+\w+\s*\(|protected\s+\w+\s+\w+\s*\(/g,
|
||||
'.cpp': /void\s+\w+\s*\(|int\s+\w+\s*\(|string\s+\w+\s*\(/g,
|
||||
'.cs': /public\s+\w+\s+\w+\s*\(|private\s+\w+\s+\w+\s*\(|protected\s+\w+\s+\w+\s*\(/g
|
||||
};
|
||||
|
||||
const pattern = patterns[extension] || /function\s+\w+\s*\(/g;
|
||||
const matches = content.match(pattern);
|
||||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
private extractImports(content: string, extension: string): string[] {
|
||||
const patterns: Record<string, RegExp> = {
|
||||
'.js': /import\s+.*?from\s+['"]([^'"]+)['"]/g,
|
||||
'.ts': /import\s+.*?from\s+['"]([^'"]+)['"]/g,
|
||||
'.py': /import\s+(\w+)|from\s+(\w+)\s+import/g,
|
||||
'.java': /import\s+([\w.]+);/g,
|
||||
'.cpp': /#include\s+[<"]([^>"]+)[>"]/g,
|
||||
'.cs': /using\s+([\w.]+);/g
|
||||
};
|
||||
|
||||
const pattern = patterns[extension];
|
||||
if (!pattern) return [];
|
||||
|
||||
const imports: string[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = pattern.exec(content)) !== null) {
|
||||
imports.push(match[1] || match[2] || match[0]);
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
private calculateComplexity(content: string): number {
|
||||
// Simple cyclomatic complexity calculation
|
||||
const complexityFactors = [
|
||||
/if\s*\(/g,
|
||||
/else\s*{/g,
|
||||
/for\s*\(/g,
|
||||
/while\s*\(/g,
|
||||
/switch\s*\(/g,
|
||||
/case\s+/g,
|
||||
/catch\s*\(/g,
|
||||
/\|\|/g,
|
||||
/&&/g
|
||||
];
|
||||
|
||||
let complexity = 1; // Base complexity
|
||||
|
||||
complexityFactors.forEach(factor => {
|
||||
const matches = content.match(factor);
|
||||
if (matches) {
|
||||
complexity += matches.length;
|
||||
}
|
||||
});
|
||||
|
||||
return complexity;
|
||||
}
|
||||
|
||||
async executeTool(toolName: string, params: any): Promise<ToolResult> {
|
||||
try {
|
||||
this.logger.info('Executing tool', { toolName, params });
|
||||
|
||||
switch (toolName) {
|
||||
case 'read_file':
|
||||
return await this.readFile(params.path);
|
||||
case 'write_file':
|
||||
return await this.writeFile(params.path, params.content);
|
||||
case 'list_directory':
|
||||
return await this.listDirectory(params.path);
|
||||
case 'search_files':
|
||||
return await this.searchFiles(params.directory, params.options);
|
||||
case 'terminal_command':
|
||||
return await this.executeTerminalCommand(params);
|
||||
case 'web_search':
|
||||
return await this.executeWebSearch(params);
|
||||
case 'analyze_code':
|
||||
return await this.analyzeCode(params.filePath);
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Unknown tool: ${toolName}`
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Tool execution failed', { error: error.message, toolName, params });
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Tool execution failed: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Nowhere_AI_Agent/src/utils/logger.ts
Normal file
116
Nowhere_AI_Agent/src/utils/logger.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import winston from 'winston';
|
||||
import { join } from 'path';
|
||||
|
||||
export class Logger {
|
||||
private logger: winston.Logger;
|
||||
|
||||
constructor(service: string) {
|
||||
const logDir = join(__dirname, '../../logs');
|
||||
|
||||
this.logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: { service },
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: join(logDir, 'error.log'),
|
||||
level: 'error',
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: join(logDir, 'combined.log'),
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Add console transport in development
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
this.logger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
info(message: string, meta?: any): void {
|
||||
this.logger.info(message, meta);
|
||||
}
|
||||
|
||||
error(message: string, meta?: any): void {
|
||||
this.logger.error(message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: any): void {
|
||||
this.logger.warn(message, meta);
|
||||
}
|
||||
|
||||
debug(message: string, meta?: any): void {
|
||||
this.logger.debug(message, meta);
|
||||
}
|
||||
|
||||
// Specialized logging for agent activities
|
||||
agentAction(action: string, userId: string, details?: any): void {
|
||||
this.info(`Agent Action: ${action}`, {
|
||||
userId,
|
||||
action,
|
||||
details,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
commandProcessed(command: string, userId: string, response: any): void {
|
||||
this.info('Command Processed', {
|
||||
userId,
|
||||
command,
|
||||
responseLength: response.response?.length || 0,
|
||||
confidence: response.confidence,
|
||||
model: response.model,
|
||||
tokens: response.tokens
|
||||
});
|
||||
}
|
||||
|
||||
voiceCommandProcessed(command: string, userId: string, confidence: number): void {
|
||||
this.info('Voice Command Processed', {
|
||||
userId,
|
||||
command,
|
||||
confidence,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
autopilotToggle(userId: string, enabled: boolean): void {
|
||||
this.info('Autopilot Toggle', {
|
||||
userId,
|
||||
enabled,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
memoryOperation(operation: string, userId: string, details?: any): void {
|
||||
this.info(`Memory Operation: ${operation}`, {
|
||||
userId,
|
||||
operation,
|
||||
details,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
errorWithContext(error: Error, context: string, userId?: string): void {
|
||||
this.error('Error with context', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
context,
|
||||
userId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
343
Nowhere_AI_Agent/src/voice/voice-processor.ts
Normal file
343
Nowhere_AI_Agent/src/voice/voice-processor.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
export interface VoiceCommand {
|
||||
command: string;
|
||||
confidence: number;
|
||||
intent: string;
|
||||
entities: any[];
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface VoiceResponse {
|
||||
text: string;
|
||||
mode: 'brief' | 'detailed' | 'silent' | 'interactive';
|
||||
audioUrl?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export class VoiceProcessor {
|
||||
private logger: Logger;
|
||||
private isListening: boolean = false;
|
||||
private recognition: any; // Web Speech API recognition
|
||||
private synthesis: any; // Web Speech API synthesis
|
||||
private currentLanguage: string = 'en-US';
|
||||
private voiceMode: 'brief' | 'detailed' | 'silent' | 'interactive' = 'brief';
|
||||
|
||||
constructor() {
|
||||
this.logger = new Logger('VoiceProcessor');
|
||||
this.initializeSpeechAPIs();
|
||||
}
|
||||
|
||||
private initializeSpeechAPIs(): void {
|
||||
try {
|
||||
// Initialize Web Speech API (for client-side simulation)
|
||||
if (typeof window !== 'undefined' && 'webkitSpeechRecognition' in window) {
|
||||
this.recognition = new (window as any).webkitSpeechRecognition();
|
||||
this.synthesis = window.speechSynthesis;
|
||||
this.setupRecognition();
|
||||
this.logger.info('Web Speech API initialized successfully');
|
||||
} else {
|
||||
this.logger.warn('Web Speech API not available, using mock implementation');
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to initialize speech APIs', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
private setupRecognition(): void {
|
||||
if (!this.recognition) return;
|
||||
|
||||
this.recognition.continuous = true;
|
||||
this.recognition.interimResults = true;
|
||||
this.recognition.lang = this.currentLanguage;
|
||||
|
||||
this.recognition.onstart = () => {
|
||||
this.isListening = true;
|
||||
this.logger.info('Voice recognition started');
|
||||
};
|
||||
|
||||
this.recognition.onend = () => {
|
||||
this.isListening = false;
|
||||
this.logger.info('Voice recognition ended');
|
||||
};
|
||||
|
||||
this.recognition.onerror = (event: any) => {
|
||||
this.logger.error('Voice recognition error', { error: event.error });
|
||||
};
|
||||
}
|
||||
|
||||
async processVoiceInput(audioData?: ArrayBuffer): Promise<VoiceCommand> {
|
||||
this.logger.info('Processing voice input', { hasAudioData: !!audioData });
|
||||
|
||||
// In a real implementation, this would process actual audio data
|
||||
// For now, we'll simulate voice command processing
|
||||
const mockCommand = this.generateMockCommand();
|
||||
|
||||
this.logger.voiceCommandProcessed(mockCommand.command, 'default', mockCommand.confidence);
|
||||
|
||||
return mockCommand;
|
||||
}
|
||||
|
||||
private generateMockCommand(): VoiceCommand {
|
||||
const commands = [
|
||||
'Hello Nowhere, show me the project structure',
|
||||
'Nowhere, analyze this code file',
|
||||
'Create a new React component',
|
||||
'Run the tests and show me the results',
|
||||
'Enable autopilot mode',
|
||||
'What do you remember from our conversation?',
|
||||
'Nowhere, help me debug this issue',
|
||||
'Generate documentation for this function'
|
||||
];
|
||||
|
||||
const randomCommand = commands[Math.floor(Math.random() * commands.length)];
|
||||
const confidence = 0.85 + Math.random() * 0.1; // 85-95% confidence
|
||||
|
||||
return {
|
||||
command: randomCommand,
|
||||
confidence,
|
||||
intent: this.parseIntent(randomCommand),
|
||||
entities: this.extractEntities(randomCommand),
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
private parseIntent(command: string): string {
|
||||
const lowerCommand = command.toLowerCase();
|
||||
|
||||
if (lowerCommand.includes('show') || lowerCommand.includes('structure')) {
|
||||
return 'show_project_structure';
|
||||
}
|
||||
if (lowerCommand.includes('analyze') || lowerCommand.includes('code')) {
|
||||
return 'analyze_code';
|
||||
}
|
||||
if (lowerCommand.includes('create') || lowerCommand.includes('component')) {
|
||||
return 'create_component';
|
||||
}
|
||||
if (lowerCommand.includes('test') || lowerCommand.includes('run')) {
|
||||
return 'run_tests';
|
||||
}
|
||||
if (lowerCommand.includes('autopilot')) {
|
||||
return 'toggle_autopilot';
|
||||
}
|
||||
if (lowerCommand.includes('remember') || lowerCommand.includes('memory')) {
|
||||
return 'retrieve_memory';
|
||||
}
|
||||
if (lowerCommand.includes('debug') || lowerCommand.includes('issue')) {
|
||||
return 'debug_issue';
|
||||
}
|
||||
if (lowerCommand.includes('documentation') || lowerCommand.includes('doc')) {
|
||||
return 'generate_documentation';
|
||||
}
|
||||
|
||||
return 'general_query';
|
||||
}
|
||||
|
||||
private extractEntities(command: string): any[] {
|
||||
const entities: any[] = [];
|
||||
const lowerCommand = command.toLowerCase();
|
||||
|
||||
// Extract file types
|
||||
const fileTypes = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'html', 'css'];
|
||||
fileTypes.forEach(type => {
|
||||
if (lowerCommand.includes(type)) {
|
||||
entities.push({ type: 'file_extension', value: type });
|
||||
}
|
||||
});
|
||||
|
||||
// Extract frameworks
|
||||
const frameworks = ['react', 'vue', 'angular', 'node', 'express'];
|
||||
frameworks.forEach(framework => {
|
||||
if (lowerCommand.includes(framework)) {
|
||||
entities.push({ type: 'framework', value: framework });
|
||||
}
|
||||
});
|
||||
|
||||
// Extract actions
|
||||
const actions = ['create', 'analyze', 'show', 'run', 'debug', 'generate'];
|
||||
actions.forEach(action => {
|
||||
if (lowerCommand.includes(action)) {
|
||||
entities.push({ type: 'action', value: action });
|
||||
}
|
||||
});
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
async startListening(): Promise<void> {
|
||||
if (this.recognition) {
|
||||
this.recognition.start();
|
||||
} else {
|
||||
this.isListening = true;
|
||||
this.logger.info('Mock voice listening started');
|
||||
}
|
||||
}
|
||||
|
||||
async stopListening(): Promise<void> {
|
||||
if (this.recognition) {
|
||||
this.recognition.stop();
|
||||
} else {
|
||||
this.isListening = false;
|
||||
this.logger.info('Mock voice listening stopped');
|
||||
}
|
||||
}
|
||||
|
||||
async speakText(text: string, mode: 'brief' | 'detailed' | 'silent' | 'interactive' = 'brief'): Promise<VoiceResponse> {
|
||||
this.logger.info('Speaking text', { textLength: text.length, mode });
|
||||
|
||||
const responseText = this.generateResponseText(text, mode);
|
||||
|
||||
if (mode === 'silent') {
|
||||
return {
|
||||
text: responseText,
|
||||
mode: 'silent'
|
||||
};
|
||||
}
|
||||
|
||||
// In a real implementation, this would use TTS
|
||||
if (this.synthesis && mode !== 'silent') {
|
||||
const utterance = new SpeechSynthesisUtterance(responseText);
|
||||
utterance.lang = this.currentLanguage;
|
||||
utterance.rate = 1.0;
|
||||
utterance.pitch = 1.0;
|
||||
|
||||
this.synthesis.speak(utterance);
|
||||
}
|
||||
|
||||
return {
|
||||
text: responseText,
|
||||
mode,
|
||||
duration: responseText.length * 0.06 // Rough estimate: 60ms per character
|
||||
};
|
||||
}
|
||||
|
||||
private generateResponseText(originalText: string, mode: string): string {
|
||||
switch (mode) {
|
||||
case 'brief':
|
||||
return this.generateBriefResponse(originalText);
|
||||
case 'detailed':
|
||||
return this.generateDetailedResponse(originalText);
|
||||
case 'interactive':
|
||||
return this.generateInteractiveResponse(originalText);
|
||||
default:
|
||||
return originalText;
|
||||
}
|
||||
}
|
||||
|
||||
private generateBriefResponse(text: string): string {
|
||||
// Extract key information for brief response
|
||||
const sentences = text.split('.');
|
||||
const keySentence = sentences[0] || text;
|
||||
return `Brief: ${keySentence.trim()}.`;
|
||||
}
|
||||
|
||||
private generateDetailedResponse(text: string): string {
|
||||
// Add more context and explanation
|
||||
return `Detailed response: ${text}\n\nThis includes comprehensive information and additional context for better understanding.`;
|
||||
}
|
||||
|
||||
private generateInteractiveResponse(text: string): string {
|
||||
// Add interactive elements
|
||||
return `${text}\n\nWould you like me to:\n1. Provide more details?\n2. Show related examples?\n3. Execute this action?`;
|
||||
}
|
||||
|
||||
async processVoiceCommand(voiceInput: string): Promise<{
|
||||
command: string;
|
||||
confidence: number;
|
||||
intent: string;
|
||||
entities: any[];
|
||||
}> {
|
||||
this.logger.info('Processing voice command', { voiceInput });
|
||||
|
||||
// Remove "Nowhere" from the beginning if present
|
||||
const cleanedInput = voiceInput.replace(/^nowhere\s*,?\s*/i, '').trim();
|
||||
|
||||
return {
|
||||
command: cleanedInput,
|
||||
confidence: 0.9,
|
||||
intent: this.parseIntent(cleanedInput),
|
||||
entities: this.extractEntities(cleanedInput)
|
||||
};
|
||||
}
|
||||
|
||||
async getVoiceStatus(): Promise<{
|
||||
isListening: boolean;
|
||||
isSpeaking: boolean;
|
||||
language: string;
|
||||
available: boolean;
|
||||
}> {
|
||||
return {
|
||||
isListening: this.isListening,
|
||||
isSpeaking: this.synthesis ? this.synthesis.speaking : false,
|
||||
language: this.currentLanguage,
|
||||
available: !!(this.recognition && this.synthesis)
|
||||
};
|
||||
}
|
||||
|
||||
async setLanguage(language: string): Promise<void> {
|
||||
this.currentLanguage = language;
|
||||
if (this.recognition) {
|
||||
this.recognition.lang = language;
|
||||
}
|
||||
this.logger.info('Voice language changed', { language });
|
||||
}
|
||||
|
||||
async setVoiceMode(mode: 'brief' | 'detailed' | 'silent' | 'interactive'): Promise<void> {
|
||||
this.voiceMode = mode;
|
||||
this.logger.info('Voice mode changed', { mode });
|
||||
}
|
||||
|
||||
// Advanced voice features
|
||||
async transcribeAudio(audioData: ArrayBuffer): Promise<string> {
|
||||
// Mock transcription
|
||||
this.logger.info('Transcribing audio', { audioSize: audioData.byteLength });
|
||||
return "Hello Nowhere, please help me with this code.";
|
||||
}
|
||||
|
||||
async generateSpeech(text: string, options?: {
|
||||
voice?: string;
|
||||
rate?: number;
|
||||
pitch?: number;
|
||||
}): Promise<ArrayBuffer> {
|
||||
// Mock speech generation
|
||||
this.logger.info('Generating speech', { textLength: text.length, options });
|
||||
return new ArrayBuffer(1024); // Mock audio data
|
||||
}
|
||||
|
||||
async detectEmotion(audioData: ArrayBuffer): Promise<{
|
||||
emotion: string;
|
||||
confidence: number;
|
||||
intensity: number;
|
||||
}> {
|
||||
// Mock emotion detection
|
||||
const emotions = ['neutral', 'happy', 'frustrated', 'excited', 'confused'];
|
||||
const randomEmotion = emotions[Math.floor(Math.random() * emotions.length)];
|
||||
|
||||
return {
|
||||
emotion: randomEmotion,
|
||||
confidence: 0.7 + Math.random() * 0.2,
|
||||
intensity: 0.5 + Math.random() * 0.5
|
||||
};
|
||||
}
|
||||
|
||||
async getAvailableVoices(): Promise<Array<{
|
||||
name: string;
|
||||
lang: string;
|
||||
default: boolean;
|
||||
}>> {
|
||||
if (this.synthesis) {
|
||||
return this.synthesis.getVoices().map((voice: any) => ({
|
||||
name: voice.name,
|
||||
lang: voice.lang,
|
||||
default: voice.default
|
||||
}));
|
||||
}
|
||||
|
||||
// Mock voices
|
||||
return [
|
||||
{ name: 'Default Voice', lang: 'en-US', default: true },
|
||||
{ name: 'Female Voice', lang: 'en-US', default: false },
|
||||
{ name: 'Male Voice', lang: 'en-US', default: false }
|
||||
];
|
||||
}
|
||||
}
|
||||
385
Nowhere_AI_Agent/src/websocket.ts
Normal file
385
Nowhere_AI_Agent/src/websocket.ts
Normal file
@@ -0,0 +1,385 @@
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { NowhereCore, NowhereContext } from './core/nowhere';
|
||||
import { Logger } from './utils/logger';
|
||||
import { verifyToken } from './middleware/auth';
|
||||
|
||||
interface WebSocketMessage {
|
||||
type: string;
|
||||
data: any;
|
||||
userId?: string;
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
const logger = new Logger('WebSocket');
|
||||
|
||||
export function setupWebSocket(io: Server, nowhere: NowhereCore): void {
|
||||
io.on('connection', (socket: Socket) => {
|
||||
logger.info('Client connected', {
|
||||
id: socket.id,
|
||||
address: socket.handshake.address,
|
||||
userAgent: socket.handshake.headers['user-agent']
|
||||
});
|
||||
|
||||
// Send welcome message
|
||||
socket.emit('welcome', {
|
||||
type: 'welcome',
|
||||
data: {
|
||||
message: 'Welcome to Nowhere AI Agent',
|
||||
version: '2.0.0',
|
||||
features: [
|
||||
'Voice Commands',
|
||||
'Autopilot Mode',
|
||||
'Real-time Communication',
|
||||
'Memory System',
|
||||
'Advanced AI Processing',
|
||||
'Multi-model Support'
|
||||
],
|
||||
sessionId: socket.id
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
|
||||
// Handle authentication
|
||||
socket.on('authenticate', async (data: { token: string }) => {
|
||||
try {
|
||||
const decoded = verifyToken(data.token);
|
||||
if (decoded) {
|
||||
socket.data.user = {
|
||||
id: decoded.id,
|
||||
email: decoded.email,
|
||||
role: decoded.role || 'user',
|
||||
permissions: decoded.permissions || []
|
||||
};
|
||||
|
||||
logger.info('Socket authenticated', {
|
||||
socketId: socket.id,
|
||||
userId: socket.data.user.id
|
||||
});
|
||||
|
||||
socket.emit('authenticated', {
|
||||
type: 'authenticated',
|
||||
data: {
|
||||
user: socket.data.user,
|
||||
message: 'Authentication successful'
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} else {
|
||||
socket.emit('auth_error', {
|
||||
type: 'auth_error',
|
||||
data: {
|
||||
message: 'Invalid token'
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('Socket authentication error', { error: error.message });
|
||||
socket.emit('auth_error', {
|
||||
type: 'auth_error',
|
||||
data: {
|
||||
message: 'Authentication failed'
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle command messages
|
||||
socket.on('command', async (message: WebSocketMessage) => {
|
||||
try {
|
||||
const userId = socket.data.user?.id || message.userId || 'default';
|
||||
|
||||
logger.info('Processing WebSocket command', {
|
||||
command: message.data.command,
|
||||
userId,
|
||||
socketId: socket.id
|
||||
});
|
||||
|
||||
const response = await nowhere.processCommand(message.data.command, userId);
|
||||
|
||||
socket.emit('response', {
|
||||
type: 'command_response',
|
||||
data: {
|
||||
response: response.response,
|
||||
actions: response.actions,
|
||||
confidence: response.confidence,
|
||||
model: response.model,
|
||||
tokens: response.tokens,
|
||||
timestamp: response.timestamp
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
|
||||
// Broadcast to other clients if it's a system command
|
||||
if (message.data.command.toLowerCase().includes('system') ||
|
||||
message.data.command.toLowerCase().includes('broadcast')) {
|
||||
socket.broadcast.emit('system_message', {
|
||||
type: 'system_message',
|
||||
data: {
|
||||
message: `System: ${response.response}`,
|
||||
userId: userId
|
||||
},
|
||||
timestamp: new Date()
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket command error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'command_error',
|
||||
data: {
|
||||
message: 'Failed to process command',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle voice command messages
|
||||
socket.on('voice_command', async (message: WebSocketMessage) => {
|
||||
try {
|
||||
const userId = socket.data.user?.id || message.userId || 'default';
|
||||
|
||||
logger.info('Processing WebSocket voice command', {
|
||||
voiceInput: message.data.voiceInput,
|
||||
userId,
|
||||
socketId: socket.id
|
||||
});
|
||||
|
||||
const response = await nowhere.processCommand(`voice: ${message.data.voiceInput}`, userId);
|
||||
|
||||
socket.emit('voice_response', {
|
||||
type: 'voice_response',
|
||||
data: {
|
||||
response: response.response,
|
||||
actions: response.actions,
|
||||
confidence: response.confidence,
|
||||
model: response.model,
|
||||
tokens: response.tokens,
|
||||
timestamp: response.timestamp
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket voice command error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'voice_error',
|
||||
data: {
|
||||
message: 'Failed to process voice command',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle autopilot messages
|
||||
socket.on('autopilot', async (message: WebSocketMessage) => {
|
||||
try {
|
||||
const userId = socket.data.user?.id || message.userId || 'default';
|
||||
const action = message.data.action; // 'enable' or 'disable'
|
||||
|
||||
logger.info('Processing autopilot action', {
|
||||
action,
|
||||
userId,
|
||||
socketId: socket.id
|
||||
});
|
||||
|
||||
const command = action === 'enable' ? 'enable autopilot mode' : 'disable autopilot mode';
|
||||
const response = await nowhere.processCommand(command, userId);
|
||||
|
||||
socket.emit('autopilot_response', {
|
||||
type: 'autopilot_response',
|
||||
data: {
|
||||
enabled: action === 'enable',
|
||||
message: response.response,
|
||||
actions: response.actions
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket autopilot error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'autopilot_error',
|
||||
data: {
|
||||
message: 'Failed to process autopilot action',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle memory operations
|
||||
socket.on('memory', async (message: WebSocketMessage) => {
|
||||
try {
|
||||
const userId = socket.data.user?.id || message.userId || 'default';
|
||||
const operation = message.data.operation; // 'get', 'clear', 'add'
|
||||
|
||||
logger.info('Processing memory operation', {
|
||||
operation,
|
||||
userId,
|
||||
socketId: socket.id
|
||||
});
|
||||
|
||||
let response;
|
||||
switch (operation) {
|
||||
case 'get':
|
||||
response = await nowhere.processCommand('show me my memory', userId);
|
||||
break;
|
||||
case 'clear':
|
||||
response = await nowhere.processCommand('clear my memory', userId);
|
||||
break;
|
||||
case 'add':
|
||||
response = await nowhere.processCommand(`remember: ${message.data.content}`, userId);
|
||||
break;
|
||||
default:
|
||||
response = await nowhere.processCommand('show me my memory', userId);
|
||||
}
|
||||
|
||||
socket.emit('memory_response', {
|
||||
type: 'memory_response',
|
||||
data: {
|
||||
operation,
|
||||
response: response.response,
|
||||
actions: response.actions
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket memory error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'memory_error',
|
||||
data: {
|
||||
message: 'Failed to process memory operation',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle status requests
|
||||
socket.on('status', async () => {
|
||||
try {
|
||||
const status = await nowhere.getStatus();
|
||||
|
||||
socket.emit('status_response', {
|
||||
type: 'status_response',
|
||||
data: status,
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket status error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'status_error',
|
||||
data: {
|
||||
message: 'Failed to get status',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle voice status requests
|
||||
socket.on('voice_status', async () => {
|
||||
try {
|
||||
socket.emit('voice_status_response', {
|
||||
type: 'voice_status_response',
|
||||
data: {
|
||||
available: true,
|
||||
isListening: false,
|
||||
isSpeaking: false,
|
||||
language: 'en-US',
|
||||
mode: 'brief'
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: true
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('WebSocket voice status error', { error: error.message });
|
||||
socket.emit('error', {
|
||||
type: 'voice_status_error',
|
||||
data: {
|
||||
message: 'Failed to get voice status',
|
||||
error: error.message
|
||||
},
|
||||
timestamp: new Date(),
|
||||
success: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle ping/pong for connection health
|
||||
socket.on('ping', () => {
|
||||
socket.emit('pong', {
|
||||
type: 'pong',
|
||||
data: {
|
||||
timestamp: Date.now()
|
||||
},
|
||||
timestamp: new Date()
|
||||
});
|
||||
});
|
||||
|
||||
// Handle disconnect
|
||||
socket.on('disconnect', (reason: string) => {
|
||||
logger.info('Client disconnected', {
|
||||
socketId: socket.id,
|
||||
reason,
|
||||
userId: socket.data.user?.id
|
||||
});
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
socket.on('error', (error: any) => {
|
||||
logger.error('Socket error', {
|
||||
socketId: socket.id,
|
||||
error: error.message
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Broadcast system messages to all connected clients
|
||||
function broadcastSystemMessage(message: string, type: string = 'info') {
|
||||
io.emit('system_broadcast', {
|
||||
type: 'system_broadcast',
|
||||
data: {
|
||||
message,
|
||||
type,
|
||||
timestamp: new Date()
|
||||
},
|
||||
timestamp: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('Shutting down WebSocket server');
|
||||
broadcastSystemMessage('Server is shutting down', 'warning');
|
||||
io.close();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('Shutting down WebSocket server');
|
||||
broadcastSystemMessage('Server is shutting down', 'warning');
|
||||
io.close();
|
||||
});
|
||||
|
||||
logger.info('WebSocket server setup complete');
|
||||
}
|
||||
Reference in New Issue
Block a user