feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { DextoAgent } from '../agent/DextoAgent.js';
|
||||
import type { AgentConfig } from '@core/agent/schemas.js';
|
||||
import type { SessionData } from './session-manager.js';
|
||||
|
||||
/**
|
||||
* Full end-to-end integration tests for chat history preservation.
|
||||
* Tests the complete flow from DextoAgent -> SessionManager -> ChatSession -> Storage
|
||||
*/
|
||||
describe('Session Integration: Chat History Preservation', () => {
|
||||
let agent: DextoAgent;
|
||||
|
||||
const testConfig: AgentConfig = {
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
llm: {
|
||||
provider: 'openai',
|
||||
model: 'gpt-5-mini',
|
||||
apiKey: 'test-key-123',
|
||||
},
|
||||
mcpServers: {},
|
||||
sessions: {
|
||||
maxSessions: 10,
|
||||
sessionTTL: 100, // 100ms for fast testing
|
||||
},
|
||||
toolConfirmation: {
|
||||
mode: 'auto-approve',
|
||||
timeout: 120000,
|
||||
},
|
||||
elicitation: {
|
||||
enabled: false,
|
||||
timeout: 120000,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
agent = new DextoAgent(testConfig);
|
||||
await agent.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (agent.isStarted()) {
|
||||
await agent.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('full integration: chat history survives session expiry through DextoAgent', async () => {
|
||||
const sessionId = 'integration-test-session';
|
||||
|
||||
// Step 1: Create session through DextoAgent
|
||||
const session = await agent.createSession(sessionId);
|
||||
expect(session.id).toBe(sessionId);
|
||||
|
||||
// Step 2: Simulate adding messages to the session
|
||||
// In a real scenario, this would happen through agent.run() calls
|
||||
// For testing, we'll access the underlying storage directly
|
||||
const storage = agent.services.storageManager;
|
||||
const messagesKey = `messages:${sessionId}`;
|
||||
const chatHistory = [
|
||||
{ role: 'user', content: 'What is 2+2?' },
|
||||
{ role: 'assistant', content: '2+2 equals 4.' },
|
||||
{ role: 'user', content: 'Thank you!' },
|
||||
{
|
||||
role: 'assistant',
|
||||
content: "You're welcome! Is there anything else I can help you with?",
|
||||
},
|
||||
];
|
||||
await storage.getDatabase().set(messagesKey, chatHistory);
|
||||
|
||||
// Step 3: Verify session exists and has history
|
||||
const activeSession = await agent.getSession(sessionId);
|
||||
expect(activeSession).toBeDefined();
|
||||
expect(activeSession!.id).toBe(sessionId);
|
||||
|
||||
const storedHistory = await storage.getDatabase().get(messagesKey);
|
||||
expect(storedHistory).toEqual(chatHistory);
|
||||
|
||||
// Step 4: Force session expiry by manipulating lastActivity timestamp
|
||||
await new Promise((resolve) => setTimeout(resolve, 150)); // Wait > TTL
|
||||
|
||||
const sessionKey = `session:${sessionId}`;
|
||||
const sessionData = await storage.getDatabase().get<SessionData>(sessionKey);
|
||||
if (sessionData) {
|
||||
sessionData.lastActivity = Date.now() - 200; // Mark as expired
|
||||
await storage.getDatabase().set(sessionKey, sessionData);
|
||||
}
|
||||
|
||||
// Access private method to manually trigger cleanup for testing session expiry behavior
|
||||
const sessionManager = agent.sessionManager;
|
||||
await (sessionManager as any).cleanupExpiredSessions();
|
||||
|
||||
// Step 5: Verify session is removed from memory but preserved in storage
|
||||
const sessionsMap = (sessionManager as any).sessions;
|
||||
expect(sessionsMap.has(sessionId)).toBe(false);
|
||||
|
||||
// But storage should still have both session metadata and chat history
|
||||
expect(await storage.getDatabase().get(sessionKey)).toBeDefined();
|
||||
expect(await storage.getDatabase().get(messagesKey)).toEqual(chatHistory);
|
||||
|
||||
// Step 6: Access session again through DextoAgent - should restore seamlessly
|
||||
const restoredSession = await agent.getSession(sessionId);
|
||||
expect(restoredSession).toBeDefined();
|
||||
expect(restoredSession!.id).toBe(sessionId);
|
||||
|
||||
// Session should be back in memory
|
||||
expect(sessionsMap.has(sessionId)).toBe(true);
|
||||
|
||||
// Chat history should still be intact
|
||||
const restoredHistory = await storage.getDatabase().get(messagesKey);
|
||||
expect(restoredHistory).toEqual(chatHistory);
|
||||
|
||||
// Step 7: Verify we can continue the conversation
|
||||
const newMessage = { role: 'user', content: 'One more question: what is 3+3?' };
|
||||
await storage.getDatabase().set(messagesKey, [...chatHistory, newMessage]);
|
||||
|
||||
const finalHistory = await storage.getDatabase().get<any[]>(messagesKey);
|
||||
expect(finalHistory).toBeDefined();
|
||||
expect(finalHistory!).toHaveLength(5);
|
||||
expect(finalHistory![4]).toEqual(newMessage);
|
||||
});
|
||||
|
||||
test('full integration: explicit session deletion removes everything', async () => {
|
||||
const sessionId = 'deletion-test-session';
|
||||
|
||||
// Create session and add history
|
||||
await agent.createSession(sessionId);
|
||||
|
||||
const storage = agent.services.storageManager;
|
||||
const messagesKey = `messages:${sessionId}`;
|
||||
const sessionKey = `session:${sessionId}`;
|
||||
const history = [{ role: 'user', content: 'Hello!' }];
|
||||
|
||||
await storage.getDatabase().set(messagesKey, history);
|
||||
|
||||
// Verify everything exists
|
||||
expect(await agent.getSession(sessionId)).toBeDefined();
|
||||
expect(await storage.getDatabase().get(sessionKey)).toBeDefined();
|
||||
expect(await storage.getDatabase().get(messagesKey)).toEqual(history);
|
||||
|
||||
// Delete session through DextoAgent
|
||||
await agent.deleteSession(sessionId);
|
||||
|
||||
// Everything should be gone including chat history
|
||||
const deletedSession = await agent.getSession(sessionId);
|
||||
expect(deletedSession).toBeUndefined();
|
||||
expect(await storage.getDatabase().get(sessionKey)).toBeUndefined();
|
||||
expect(await storage.getDatabase().get(messagesKey)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('full integration: multiple concurrent sessions with independent histories', async () => {
|
||||
const sessionIds = ['concurrent-1', 'concurrent-2', 'concurrent-3'];
|
||||
const histories = sessionIds.map((_, index) => [
|
||||
{ role: 'user', content: `Message from session ${index + 1}` },
|
||||
{ role: 'assistant', content: `Response to session ${index + 1}` },
|
||||
]);
|
||||
|
||||
// Create multiple sessions with different histories
|
||||
const storage = agent.services.storageManager;
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
await agent.createSession(sessionIds[i]);
|
||||
await storage.getDatabase().set(`messages:${sessionIds[i]}`, histories[i]);
|
||||
}
|
||||
|
||||
// Verify all sessions exist and have correct histories
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
const sessionId = sessionIds[i]!;
|
||||
const session = await agent.getSession(sessionId);
|
||||
expect(session).toBeDefined();
|
||||
expect(session!.id).toBe(sessionId);
|
||||
|
||||
const history = await storage.getDatabase().get(`messages:${sessionId}`);
|
||||
expect(history).toEqual(histories[i]);
|
||||
}
|
||||
|
||||
// Force expiry and cleanup for all sessions
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
for (const sessionId of sessionIds) {
|
||||
const sessionData = await storage
|
||||
.getDatabase()
|
||||
.get<SessionData>(`session:${sessionId}`);
|
||||
if (sessionData) {
|
||||
sessionData.lastActivity = Date.now() - 200;
|
||||
await storage.getDatabase().set(`session:${sessionId}`, sessionData);
|
||||
}
|
||||
}
|
||||
|
||||
const sessionManager = agent.sessionManager;
|
||||
await (sessionManager as any).cleanupExpiredSessions();
|
||||
|
||||
// All should be removed from memory
|
||||
const sessionsMap = (sessionManager as any).sessions;
|
||||
sessionIds.forEach((id) => {
|
||||
expect(sessionsMap.has(id)).toBe(false);
|
||||
});
|
||||
|
||||
// But histories should be preserved in storage
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
const history = await storage.getDatabase().get(`messages:${sessionIds[i]}`);
|
||||
expect(history).toEqual(histories[i]);
|
||||
}
|
||||
|
||||
// Restore sessions one by one and verify independent operation
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
const sessionId = sessionIds[i]!;
|
||||
const restoredSession = await agent.getSession(sessionId);
|
||||
expect(restoredSession).toBeDefined();
|
||||
expect(restoredSession!.id).toBe(sessionId);
|
||||
|
||||
// Verify the session is back in memory
|
||||
expect(sessionsMap.has(sessionId)).toBe(true);
|
||||
|
||||
// Verify history is still intact and independent
|
||||
const history = await storage.getDatabase().get(`messages:${sessionId}`);
|
||||
expect(history).toEqual(histories[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// Note: Activity-based expiry prevention test removed due to timing complexities
|
||||
// The core functionality (chat history preservation) is thoroughly tested above
|
||||
});
|
||||
Reference in New Issue
Block a user