@wundr.io/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +551 -0
- package/bin/wundr.js +39 -0
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +339 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +612 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +437 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +587 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +32 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +570 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +39 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +563 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +481 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +537 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +480 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +584 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +649 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +399 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +610 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +682 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +730 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +623 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +416 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +739 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +119 -0
- package/src/ai/ai-service.ts +595 -0
- package/src/ai/claude-client.ts +490 -0
- package/src/ai/conversation-manager.ts +907 -0
- package/src/ai/index.ts +8 -0
- package/src/cli.ts +202 -0
- package/src/commands/ai.ts +995 -0
- package/src/commands/analyze-optimized.ts +641 -0
- package/src/commands/analyze.ts +576 -0
- package/src/commands/batch.ts +935 -0
- package/src/commands/chat.ts +876 -0
- package/src/commands/claude-init.ts +715 -0
- package/src/commands/claude-setup.ts +697 -0
- package/src/commands/computer-setup-commands.ts +709 -0
- package/src/commands/computer-setup.ts +565 -0
- package/src/commands/create-command.ts +175 -0
- package/src/commands/create.ts +727 -0
- package/src/commands/dashboard.ts +691 -0
- package/src/commands/govern.ts +635 -0
- package/src/commands/init.ts +677 -0
- package/src/commands/performance-optimizer.ts +864 -0
- package/src/commands/plugins.ts +848 -0
- package/src/commands/setup.ts +508 -0
- package/src/commands/test-init.ts +242 -0
- package/src/commands/test.ts +264 -0
- package/src/commands/watch.ts +755 -0
- package/src/context/context-manager.ts +546 -0
- package/src/context/index.ts +9 -0
- package/src/context/session-manager.ts +1019 -0
- package/src/index.ts +64 -0
- package/src/interactive/interactive-mode.ts +830 -0
- package/src/nlp/command-mapper.ts +885 -0
- package/src/nlp/command-parser.ts +564 -0
- package/src/nlp/index.ts +4 -0
- package/src/nlp/intent-classifier.ts +458 -0
- package/src/nlp/intent-parser.ts +1101 -0
- package/src/plugins/plugin-manager.ts +744 -0
- package/src/types/index.ts +252 -0
- package/src/types/modules.d.ts +56 -0
- package/src/utils/config-manager.ts +391 -0
- package/src/utils/error-handler.ts +192 -0
- package/src/utils/logger.ts +104 -0
- package/templates/batch/ci-cd.yaml +62 -0
- package/templates/component/{{fileName}}.test.tsx +17 -0
- package/templates/component/{{fileName}}.tsx +21 -0
- package/templates/service/{{fileName}}.ts +98 -0
- package/templates/wundr-test.config.js +0 -0
- package/test-suites/api/health.spec.ts +134 -0
- package/test-suites/helpers/test-config.ts +84 -0
- package/test-suites/ui/accessibility.spec.ts +102 -0
- package/test-suites/ui/smoke.spec.ts +92 -0
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { ClaudeClient, ClaudeMessage } from './claude-client';
|
|
5
|
+
import { ChatSession, ChatMessage } from '../types';
|
|
6
|
+
import { logger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for conversation management
|
|
10
|
+
*/
|
|
11
|
+
export interface ConversationConfig {
|
|
12
|
+
maxHistoryLength: number;
|
|
13
|
+
contextWindowSize: number;
|
|
14
|
+
sessionTimeout: number; // minutes
|
|
15
|
+
persistencePath: string;
|
|
16
|
+
autoSave: boolean;
|
|
17
|
+
compressionThreshold: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Session metadata for enhanced tracking
|
|
22
|
+
*/
|
|
23
|
+
export interface SessionMetadata {
|
|
24
|
+
tokenUsage: {
|
|
25
|
+
total: number;
|
|
26
|
+
prompt: number;
|
|
27
|
+
completion: number;
|
|
28
|
+
};
|
|
29
|
+
performance: {
|
|
30
|
+
averageResponseTime: number;
|
|
31
|
+
totalRequests: number;
|
|
32
|
+
errorCount: number;
|
|
33
|
+
};
|
|
34
|
+
context: {
|
|
35
|
+
projectPath?: string;
|
|
36
|
+
currentCommand?: string;
|
|
37
|
+
userPreferences: Record<string, any>;
|
|
38
|
+
workflow?: string[];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Enhanced chat session with metadata
|
|
44
|
+
*/
|
|
45
|
+
export interface EnhancedChatSession extends ChatSession {
|
|
46
|
+
metadata: SessionMetadata;
|
|
47
|
+
tags: string[];
|
|
48
|
+
archived: boolean;
|
|
49
|
+
lastAccessed: Date;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Conversation manager handles session persistence, context optimization, and memory management
|
|
54
|
+
*/
|
|
55
|
+
export class ConversationManager extends EventEmitter {
|
|
56
|
+
private activeSessions: Map<string, EnhancedChatSession>;
|
|
57
|
+
private claudeClient: ClaudeClient;
|
|
58
|
+
private config: ConversationConfig;
|
|
59
|
+
private sessionCleanupTimer?: NodeJS.Timeout;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
claudeClient: ClaudeClient,
|
|
63
|
+
config: Partial<ConversationConfig> = {}
|
|
64
|
+
) {
|
|
65
|
+
super();
|
|
66
|
+
|
|
67
|
+
this.claudeClient = claudeClient;
|
|
68
|
+
this.config = {
|
|
69
|
+
maxHistoryLength: 100,
|
|
70
|
+
contextWindowSize: 20,
|
|
71
|
+
sessionTimeout: 60, // 1 hour
|
|
72
|
+
persistencePath: path.join(process.cwd(), '.wundr', 'conversations'),
|
|
73
|
+
autoSave: true,
|
|
74
|
+
compressionThreshold: 50,
|
|
75
|
+
...config,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.activeSessions = new Map();
|
|
79
|
+
|
|
80
|
+
this.initializeManager();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a new conversation session
|
|
85
|
+
*/
|
|
86
|
+
async createSession(
|
|
87
|
+
options: {
|
|
88
|
+
id?: string;
|
|
89
|
+
model?: string;
|
|
90
|
+
context?: string;
|
|
91
|
+
tags?: string[];
|
|
92
|
+
metadata?: Partial<SessionMetadata>;
|
|
93
|
+
} = {}
|
|
94
|
+
): Promise<EnhancedChatSession> {
|
|
95
|
+
const sessionId =
|
|
96
|
+
options.id ||
|
|
97
|
+
`session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
98
|
+
|
|
99
|
+
const session: EnhancedChatSession = {
|
|
100
|
+
id: sessionId,
|
|
101
|
+
model: options.model || this.claudeClient.getModelInfo().model,
|
|
102
|
+
context: options.context,
|
|
103
|
+
history: [],
|
|
104
|
+
created: new Date(),
|
|
105
|
+
updated: new Date(),
|
|
106
|
+
archived: false,
|
|
107
|
+
lastAccessed: new Date(),
|
|
108
|
+
tags: options.tags || [],
|
|
109
|
+
metadata: {
|
|
110
|
+
tokenUsage: { total: 0, prompt: 0, completion: 0 },
|
|
111
|
+
performance: {
|
|
112
|
+
averageResponseTime: 0,
|
|
113
|
+
totalRequests: 0,
|
|
114
|
+
errorCount: 0,
|
|
115
|
+
},
|
|
116
|
+
context: {
|
|
117
|
+
userPreferences: {},
|
|
118
|
+
...options.metadata?.context,
|
|
119
|
+
},
|
|
120
|
+
...options.metadata,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.activeSessions.set(sessionId, session);
|
|
125
|
+
|
|
126
|
+
if (this.config.autoSave) {
|
|
127
|
+
await this.persistSession(session);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.emit('session_created', { sessionId, session });
|
|
131
|
+
logger.debug(`Created conversation session: ${sessionId}`);
|
|
132
|
+
|
|
133
|
+
return session;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Load an existing session
|
|
138
|
+
*/
|
|
139
|
+
async loadSession(sessionId: string): Promise<EnhancedChatSession | null> {
|
|
140
|
+
// Check active sessions first
|
|
141
|
+
if (this.activeSessions.has(sessionId)) {
|
|
142
|
+
const session = this.activeSessions.get(sessionId)!;
|
|
143
|
+
session.lastAccessed = new Date();
|
|
144
|
+
return session;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Load from persistence
|
|
148
|
+
try {
|
|
149
|
+
const sessionPath = this.getSessionPath(sessionId);
|
|
150
|
+
if (await fs.pathExists(sessionPath)) {
|
|
151
|
+
const data = await fs.readJson(sessionPath);
|
|
152
|
+
const session = this.deserializeSession(data);
|
|
153
|
+
|
|
154
|
+
session.lastAccessed = new Date();
|
|
155
|
+
this.activeSessions.set(sessionId, session);
|
|
156
|
+
|
|
157
|
+
this.emit('session_loaded', { sessionId, session });
|
|
158
|
+
logger.debug(`Loaded conversation session: ${sessionId}`);
|
|
159
|
+
|
|
160
|
+
return session;
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(`Failed to load session ${sessionId}:`, error);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Send a message in a conversation session
|
|
171
|
+
*/
|
|
172
|
+
async sendMessage(
|
|
173
|
+
sessionId: string,
|
|
174
|
+
message: string,
|
|
175
|
+
options: {
|
|
176
|
+
systemPrompt?: string;
|
|
177
|
+
streaming?: boolean;
|
|
178
|
+
metadata?: Record<string, any>;
|
|
179
|
+
} = {}
|
|
180
|
+
): Promise<string | AsyncGenerator<string, void, unknown>> {
|
|
181
|
+
const session = await this.loadSession(sessionId);
|
|
182
|
+
if (!session) {
|
|
183
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const startTime = Date.now();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Add user message to history
|
|
190
|
+
const userMessage: ChatMessage = {
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: message,
|
|
193
|
+
timestamp: new Date(),
|
|
194
|
+
metadata: options.metadata,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
session.history.push(userMessage);
|
|
198
|
+
|
|
199
|
+
// Optimize conversation context
|
|
200
|
+
const contextMessages = this.optimizeContext(session);
|
|
201
|
+
|
|
202
|
+
// Convert to Claude message format
|
|
203
|
+
const claudeMessages: ClaudeMessage[] = contextMessages.map(msg => ({
|
|
204
|
+
role: msg.role,
|
|
205
|
+
content: msg.content,
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
// Send to Claude
|
|
209
|
+
let response: string | AsyncGenerator<string, void, unknown>;
|
|
210
|
+
|
|
211
|
+
if (options.streaming) {
|
|
212
|
+
response = this.claudeClient.streamConversation(
|
|
213
|
+
claudeMessages,
|
|
214
|
+
options.systemPrompt
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
response = await this.claudeClient.sendConversation(
|
|
218
|
+
claudeMessages,
|
|
219
|
+
options.systemPrompt
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Handle streaming response
|
|
224
|
+
if (options.streaming) {
|
|
225
|
+
return this.handleStreamingResponse(
|
|
226
|
+
session,
|
|
227
|
+
response as AsyncGenerator<string, void, unknown>,
|
|
228
|
+
startTime
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
return await this.handleSyncResponse(
|
|
232
|
+
session,
|
|
233
|
+
response as string,
|
|
234
|
+
startTime
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
session.metadata.performance.errorCount++;
|
|
239
|
+
this.emit('message_error', { sessionId, error, message });
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get conversation history with optional filtering
|
|
246
|
+
*/
|
|
247
|
+
getHistory(
|
|
248
|
+
sessionId: string,
|
|
249
|
+
options: {
|
|
250
|
+
limit?: number;
|
|
251
|
+
from?: Date;
|
|
252
|
+
to?: Date;
|
|
253
|
+
roles?: ('user' | 'assistant' | 'system')[];
|
|
254
|
+
} = {}
|
|
255
|
+
): ChatMessage[] {
|
|
256
|
+
const session = this.activeSessions.get(sessionId);
|
|
257
|
+
if (!session) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let history = [...session.history];
|
|
262
|
+
|
|
263
|
+
// Apply filters
|
|
264
|
+
if (options.from) {
|
|
265
|
+
history = history.filter(msg => msg.timestamp >= options.from!);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (options.to) {
|
|
269
|
+
history = history.filter(msg => msg.timestamp <= options.to!);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (options.roles) {
|
|
273
|
+
history = history.filter(msg => options.roles!.includes(msg.role));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (options.limit) {
|
|
277
|
+
history = history.slice(-options.limit);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return history;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Add context to a conversation session
|
|
285
|
+
*/
|
|
286
|
+
async addContext(
|
|
287
|
+
sessionId: string,
|
|
288
|
+
contextType: 'project' | 'command' | 'preference' | 'workflow',
|
|
289
|
+
contextData: any
|
|
290
|
+
): Promise<void> {
|
|
291
|
+
const session = await this.loadSession(sessionId);
|
|
292
|
+
if (!session) {
|
|
293
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
switch (contextType) {
|
|
297
|
+
case 'project':
|
|
298
|
+
session.metadata.context.projectPath = contextData.path;
|
|
299
|
+
break;
|
|
300
|
+
case 'command':
|
|
301
|
+
session.metadata.context.currentCommand = contextData.command;
|
|
302
|
+
break;
|
|
303
|
+
case 'preference':
|
|
304
|
+
Object.assign(session.metadata.context.userPreferences, contextData);
|
|
305
|
+
break;
|
|
306
|
+
case 'workflow':
|
|
307
|
+
if (!session.metadata.context.workflow) {
|
|
308
|
+
session.metadata.context.workflow = [];
|
|
309
|
+
}
|
|
310
|
+
session.metadata.context.workflow.push(...contextData);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
session.updated = new Date();
|
|
315
|
+
|
|
316
|
+
if (this.config.autoSave) {
|
|
317
|
+
await this.persistSession(session);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.emit('context_updated', { sessionId, contextType, contextData });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Archive old or inactive sessions
|
|
325
|
+
*/
|
|
326
|
+
async archiveSessions(
|
|
327
|
+
criteria: {
|
|
328
|
+
olderThan?: number; // days
|
|
329
|
+
inactiveFor?: number; // days
|
|
330
|
+
maxSessions?: number;
|
|
331
|
+
} = {}
|
|
332
|
+
): Promise<string[]> {
|
|
333
|
+
const archivedSessions: string[] = [];
|
|
334
|
+
const now = new Date();
|
|
335
|
+
|
|
336
|
+
for (const [sessionId, session] of this.activeSessions) {
|
|
337
|
+
let shouldArchive = false;
|
|
338
|
+
|
|
339
|
+
if (criteria.olderThan) {
|
|
340
|
+
const ageInDays =
|
|
341
|
+
(now.getTime() - session.created.getTime()) / (1000 * 60 * 60 * 24);
|
|
342
|
+
if (ageInDays > criteria.olderThan) {
|
|
343
|
+
shouldArchive = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (criteria.inactiveFor) {
|
|
348
|
+
const inactiveInDays =
|
|
349
|
+
(now.getTime() - session.lastAccessed.getTime()) /
|
|
350
|
+
(1000 * 60 * 60 * 24);
|
|
351
|
+
if (inactiveInDays > criteria.inactiveFor) {
|
|
352
|
+
shouldArchive = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (shouldArchive) {
|
|
357
|
+
session.archived = true;
|
|
358
|
+
await this.persistSession(session);
|
|
359
|
+
this.activeSessions.delete(sessionId);
|
|
360
|
+
archivedSessions.push(sessionId);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Handle maxSessions limit
|
|
365
|
+
if (
|
|
366
|
+
criteria.maxSessions &&
|
|
367
|
+
this.activeSessions.size > criteria.maxSessions
|
|
368
|
+
) {
|
|
369
|
+
const sessionsByAccess = Array.from(this.activeSessions.entries()).sort(
|
|
370
|
+
([, a], [, b]) => a.lastAccessed.getTime() - b.lastAccessed.getTime()
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const excessCount = this.activeSessions.size - criteria.maxSessions;
|
|
374
|
+
const toArchive = sessionsByAccess.slice(0, excessCount);
|
|
375
|
+
|
|
376
|
+
for (const [sessionId, session] of toArchive) {
|
|
377
|
+
session.archived = true;
|
|
378
|
+
await this.persistSession(session);
|
|
379
|
+
this.activeSessions.delete(sessionId);
|
|
380
|
+
archivedSessions.push(sessionId);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.emit('sessions_archived', {
|
|
385
|
+
count: archivedSessions.length,
|
|
386
|
+
sessionIds: archivedSessions,
|
|
387
|
+
});
|
|
388
|
+
logger.debug(`Archived ${archivedSessions.length} sessions`);
|
|
389
|
+
|
|
390
|
+
return archivedSessions;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Search conversations by content or metadata
|
|
395
|
+
*/
|
|
396
|
+
async searchConversations(query: {
|
|
397
|
+
text?: string;
|
|
398
|
+
tags?: string[];
|
|
399
|
+
dateRange?: { from: Date; to: Date };
|
|
400
|
+
model?: string;
|
|
401
|
+
limit?: number;
|
|
402
|
+
}): Promise<
|
|
403
|
+
Array<{ sessionId: string; matches: ChatMessage[]; score: number }>
|
|
404
|
+
> {
|
|
405
|
+
const results: Array<{
|
|
406
|
+
sessionId: string;
|
|
407
|
+
matches: ChatMessage[];
|
|
408
|
+
score: number;
|
|
409
|
+
}> = [];
|
|
410
|
+
|
|
411
|
+
// Search active sessions
|
|
412
|
+
for (const [sessionId, session] of this.activeSessions) {
|
|
413
|
+
const matches = this.searchSession(session, query);
|
|
414
|
+
if (matches.length > 0) {
|
|
415
|
+
results.push({
|
|
416
|
+
sessionId,
|
|
417
|
+
matches,
|
|
418
|
+
score: this.calculateSearchScore(matches, query),
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Search persisted sessions if needed
|
|
424
|
+
// This could be expensive, so implement pagination/indexing for production
|
|
425
|
+
|
|
426
|
+
// Sort by score and apply limit
|
|
427
|
+
results.sort((a, b) => b.score - a.score);
|
|
428
|
+
|
|
429
|
+
if (query.limit) {
|
|
430
|
+
results.splice(query.limit);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return results;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get session statistics
|
|
438
|
+
*/
|
|
439
|
+
getSessionStats(sessionId?: string): any {
|
|
440
|
+
if (sessionId) {
|
|
441
|
+
const session = this.activeSessions.get(sessionId);
|
|
442
|
+
if (!session) return null;
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
messageCount: session.history.length,
|
|
446
|
+
tokenUsage: session.metadata.tokenUsage,
|
|
447
|
+
performance: session.metadata.performance,
|
|
448
|
+
created: session.created,
|
|
449
|
+
lastAccessed: session.lastAccessed,
|
|
450
|
+
tags: session.tags,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Global stats
|
|
455
|
+
const totalSessions = this.activeSessions.size;
|
|
456
|
+
const totalMessages = Array.from(this.activeSessions.values()).reduce(
|
|
457
|
+
(sum, session) => sum + session.history.length,
|
|
458
|
+
0
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const totalTokens = Array.from(this.activeSessions.values()).reduce(
|
|
462
|
+
(sum, session) => sum + session.metadata.tokenUsage.total,
|
|
463
|
+
0
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
totalSessions,
|
|
468
|
+
totalMessages,
|
|
469
|
+
totalTokens,
|
|
470
|
+
averageMessagesPerSession:
|
|
471
|
+
totalSessions > 0 ? totalMessages / totalSessions : 0,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Clean up expired sessions
|
|
477
|
+
*/
|
|
478
|
+
async cleanup(): Promise<void> {
|
|
479
|
+
const expiredSessionIds: string[] = [];
|
|
480
|
+
const now = new Date();
|
|
481
|
+
const timeoutMs = this.config.sessionTimeout * 60 * 1000;
|
|
482
|
+
|
|
483
|
+
for (const [sessionId, session] of this.activeSessions) {
|
|
484
|
+
const timeSinceAccess = now.getTime() - session.lastAccessed.getTime();
|
|
485
|
+
|
|
486
|
+
if (timeSinceAccess > timeoutMs) {
|
|
487
|
+
expiredSessionIds.push(sessionId);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
for (const sessionId of expiredSessionIds) {
|
|
492
|
+
const session = this.activeSessions.get(sessionId)!;
|
|
493
|
+
|
|
494
|
+
// Persist before removing
|
|
495
|
+
if (this.config.autoSave) {
|
|
496
|
+
await this.persistSession(session);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
this.activeSessions.delete(sessionId);
|
|
500
|
+
this.emit('session_expired', { sessionId });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (expiredSessionIds.length > 0) {
|
|
504
|
+
logger.debug(`Cleaned up ${expiredSessionIds.length} expired sessions`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Export conversation data
|
|
510
|
+
*/
|
|
511
|
+
async exportSession(
|
|
512
|
+
sessionId: string,
|
|
513
|
+
format: 'json' | 'markdown' | 'csv'
|
|
514
|
+
): Promise<string> {
|
|
515
|
+
const session = await this.loadSession(sessionId);
|
|
516
|
+
if (!session) {
|
|
517
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
switch (format) {
|
|
521
|
+
case 'json':
|
|
522
|
+
return JSON.stringify(session, null, 2);
|
|
523
|
+
case 'markdown':
|
|
524
|
+
return this.sessionToMarkdown(session);
|
|
525
|
+
case 'csv':
|
|
526
|
+
return this.sessionToCsv(session);
|
|
527
|
+
default:
|
|
528
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Import conversation data
|
|
534
|
+
*/
|
|
535
|
+
async importSession(data: string, format: 'json'): Promise<string> {
|
|
536
|
+
if (format !== 'json') {
|
|
537
|
+
throw new Error(`Unsupported import format: ${format}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const sessionData = JSON.parse(data);
|
|
541
|
+
const session = this.deserializeSession(sessionData);
|
|
542
|
+
|
|
543
|
+
// Generate new ID to avoid conflicts
|
|
544
|
+
session.id = `imported-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
545
|
+
session.created = new Date();
|
|
546
|
+
session.lastAccessed = new Date();
|
|
547
|
+
|
|
548
|
+
this.activeSessions.set(session.id, session);
|
|
549
|
+
|
|
550
|
+
if (this.config.autoSave) {
|
|
551
|
+
await this.persistSession(session);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
this.emit('session_imported', { sessionId: session.id });
|
|
555
|
+
return session.id;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* List all sessions with pagination
|
|
560
|
+
*/
|
|
561
|
+
async listSessions(
|
|
562
|
+
options: {
|
|
563
|
+
limit?: number;
|
|
564
|
+
offset?: number;
|
|
565
|
+
includeArchived?: boolean;
|
|
566
|
+
sortBy?: 'created' | 'updated' | 'lastAccessed';
|
|
567
|
+
sortOrder?: 'asc' | 'desc';
|
|
568
|
+
} = {}
|
|
569
|
+
): Promise<{
|
|
570
|
+
sessions: Array<
|
|
571
|
+
Pick<
|
|
572
|
+
EnhancedChatSession,
|
|
573
|
+
'id' | 'created' | 'updated' | 'lastAccessed' | 'tags' | 'archived'
|
|
574
|
+
>
|
|
575
|
+
>;
|
|
576
|
+
total: number;
|
|
577
|
+
hasMore: boolean;
|
|
578
|
+
}> {
|
|
579
|
+
const allSessions = Array.from(this.activeSessions.values());
|
|
580
|
+
|
|
581
|
+
// Filter archived if needed
|
|
582
|
+
const filteredSessions = options.includeArchived
|
|
583
|
+
? allSessions
|
|
584
|
+
: allSessions.filter(s => !s.archived);
|
|
585
|
+
|
|
586
|
+
// Sort
|
|
587
|
+
const sortBy = options.sortBy || 'lastAccessed';
|
|
588
|
+
const sortOrder = options.sortOrder || 'desc';
|
|
589
|
+
|
|
590
|
+
filteredSessions.sort((a, b) => {
|
|
591
|
+
const aValue = a[sortBy].getTime();
|
|
592
|
+
const bValue = b[sortBy].getTime();
|
|
593
|
+
return sortOrder === 'desc' ? bValue - aValue : aValue - bValue;
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Paginate
|
|
597
|
+
const offset = options.offset || 0;
|
|
598
|
+
const limit = options.limit || 50;
|
|
599
|
+
const paginatedSessions = filteredSessions.slice(offset, offset + limit);
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
sessions: paginatedSessions.map(s => ({
|
|
603
|
+
id: s.id,
|
|
604
|
+
created: s.created,
|
|
605
|
+
updated: s.updated,
|
|
606
|
+
lastAccessed: s.lastAccessed,
|
|
607
|
+
tags: s.tags,
|
|
608
|
+
archived: s.archived,
|
|
609
|
+
})),
|
|
610
|
+
total: filteredSessions.length,
|
|
611
|
+
hasMore: offset + limit < filteredSessions.length,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Private helper methods
|
|
616
|
+
|
|
617
|
+
private async initializeManager(): Promise<void> {
|
|
618
|
+
// Ensure persistence directory exists
|
|
619
|
+
await fs.ensureDir(this.config.persistencePath);
|
|
620
|
+
|
|
621
|
+
// Start cleanup timer
|
|
622
|
+
this.sessionCleanupTimer = setInterval(
|
|
623
|
+
() => {
|
|
624
|
+
this.cleanup().catch(error => {
|
|
625
|
+
logger.error('Session cleanup failed:', error);
|
|
626
|
+
});
|
|
627
|
+
},
|
|
628
|
+
10 * 60 * 1000
|
|
629
|
+
); // Every 10 minutes
|
|
630
|
+
|
|
631
|
+
logger.debug('Conversation manager initialized');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private optimizeContext(session: EnhancedChatSession): ChatMessage[] {
|
|
635
|
+
const { contextWindowSize, compressionThreshold } = this.config;
|
|
636
|
+
|
|
637
|
+
if (session.history.length <= contextWindowSize) {
|
|
638
|
+
return session.history;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Keep system messages and recent messages
|
|
642
|
+
const systemMessages = session.history.filter(msg => msg.role === 'system');
|
|
643
|
+
const recentMessages = session.history.slice(
|
|
644
|
+
-contextWindowSize + systemMessages.length
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
// If we still have too many messages, apply compression
|
|
648
|
+
if (recentMessages.length > compressionThreshold) {
|
|
649
|
+
return this.compressHistory([...systemMessages, ...recentMessages]);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return [...systemMessages, ...recentMessages];
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private compressHistory(messages: ChatMessage[]): ChatMessage[] {
|
|
656
|
+
// Simple compression: summarize older messages
|
|
657
|
+
// In a production system, you might use Claude to generate summaries
|
|
658
|
+
|
|
659
|
+
const recentCount = 10;
|
|
660
|
+
const recent = messages.slice(-recentCount);
|
|
661
|
+
const older = messages.slice(0, -recentCount);
|
|
662
|
+
|
|
663
|
+
if (older.length === 0) return recent;
|
|
664
|
+
|
|
665
|
+
const summary: ChatMessage = {
|
|
666
|
+
role: 'system',
|
|
667
|
+
content: `[Previous conversation summary: ${older.length} messages covering topics and discussions that led to the current context]`,
|
|
668
|
+
timestamp: new Date(),
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
return [summary, ...recent];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private async handleSyncResponse(
|
|
675
|
+
session: EnhancedChatSession,
|
|
676
|
+
response: string,
|
|
677
|
+
startTime: number
|
|
678
|
+
): Promise<string> {
|
|
679
|
+
const endTime = Date.now();
|
|
680
|
+
const responseTime = endTime - startTime;
|
|
681
|
+
|
|
682
|
+
// Add AI response to history
|
|
683
|
+
const aiMessage: ChatMessage = {
|
|
684
|
+
role: 'assistant',
|
|
685
|
+
content: response,
|
|
686
|
+
timestamp: new Date(),
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
session.history.push(aiMessage);
|
|
690
|
+
session.updated = new Date();
|
|
691
|
+
session.lastAccessed = new Date();
|
|
692
|
+
|
|
693
|
+
// Update performance metrics
|
|
694
|
+
this.updatePerformanceMetrics(session, responseTime);
|
|
695
|
+
|
|
696
|
+
if (this.config.autoSave) {
|
|
697
|
+
await this.persistSession(session);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
this.emit('message_complete', {
|
|
701
|
+
sessionId: session.id,
|
|
702
|
+
responseTime,
|
|
703
|
+
tokenCount: response.length, // Rough approximation
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
return response;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private async *handleStreamingResponse(
|
|
710
|
+
session: EnhancedChatSession,
|
|
711
|
+
responseGenerator: AsyncGenerator<string, void, unknown>,
|
|
712
|
+
startTime: number
|
|
713
|
+
): AsyncGenerator<string, void, unknown> {
|
|
714
|
+
let fullResponse = '';
|
|
715
|
+
let firstChunk = true;
|
|
716
|
+
|
|
717
|
+
for await (const chunk of responseGenerator) {
|
|
718
|
+
if (firstChunk) {
|
|
719
|
+
this.emit('stream_started', { sessionId: session.id });
|
|
720
|
+
firstChunk = false;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
fullResponse += chunk;
|
|
724
|
+
yield chunk;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const endTime = Date.now();
|
|
728
|
+
const responseTime = endTime - startTime;
|
|
729
|
+
|
|
730
|
+
// Add complete response to history
|
|
731
|
+
const aiMessage: ChatMessage = {
|
|
732
|
+
role: 'assistant',
|
|
733
|
+
content: fullResponse,
|
|
734
|
+
timestamp: new Date(),
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
session.history.push(aiMessage);
|
|
738
|
+
session.updated = new Date();
|
|
739
|
+
session.lastAccessed = new Date();
|
|
740
|
+
|
|
741
|
+
this.updatePerformanceMetrics(session, responseTime);
|
|
742
|
+
|
|
743
|
+
if (this.config.autoSave) {
|
|
744
|
+
await this.persistSession(session);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
this.emit('stream_complete', {
|
|
748
|
+
sessionId: session.id,
|
|
749
|
+
responseTime,
|
|
750
|
+
tokenCount: fullResponse.length,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private updatePerformanceMetrics(
|
|
755
|
+
session: EnhancedChatSession,
|
|
756
|
+
responseTime: number
|
|
757
|
+
): void {
|
|
758
|
+
const perf = session.metadata.performance;
|
|
759
|
+
perf.totalRequests++;
|
|
760
|
+
perf.averageResponseTime =
|
|
761
|
+
(perf.averageResponseTime * (perf.totalRequests - 1) + responseTime) /
|
|
762
|
+
perf.totalRequests;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private async persistSession(session: EnhancedChatSession): Promise<void> {
|
|
766
|
+
const sessionPath = this.getSessionPath(session.id);
|
|
767
|
+
const serialized = this.serializeSession(session);
|
|
768
|
+
|
|
769
|
+
await fs.ensureDir(path.dirname(sessionPath));
|
|
770
|
+
await fs.writeJson(sessionPath, serialized, { spaces: 2 });
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private getSessionPath(sessionId: string): string {
|
|
774
|
+
return path.join(this.config.persistencePath, `${sessionId}.json`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private serializeSession(session: EnhancedChatSession): any {
|
|
778
|
+
return {
|
|
779
|
+
...session,
|
|
780
|
+
created: session.created.toISOString(),
|
|
781
|
+
updated: session.updated.toISOString(),
|
|
782
|
+
lastAccessed: session.lastAccessed.toISOString(),
|
|
783
|
+
history: session.history.map(msg => ({
|
|
784
|
+
...msg,
|
|
785
|
+
timestamp: msg.timestamp.toISOString(),
|
|
786
|
+
})),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private deserializeSession(data: any): EnhancedChatSession {
|
|
791
|
+
return {
|
|
792
|
+
...data,
|
|
793
|
+
created: new Date(data.created),
|
|
794
|
+
updated: new Date(data.updated),
|
|
795
|
+
lastAccessed: new Date(data.lastAccessed),
|
|
796
|
+
history: data.history.map((msg: any) => ({
|
|
797
|
+
...msg,
|
|
798
|
+
timestamp: new Date(msg.timestamp),
|
|
799
|
+
})),
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private searchSession(
|
|
804
|
+
session: EnhancedChatSession,
|
|
805
|
+
query: any
|
|
806
|
+
): ChatMessage[] {
|
|
807
|
+
let matches = session.history;
|
|
808
|
+
|
|
809
|
+
if (query.text) {
|
|
810
|
+
const searchText = query.text.toLowerCase();
|
|
811
|
+
matches = matches.filter(msg =>
|
|
812
|
+
msg.content.toLowerCase().includes(searchText)
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (query.dateRange) {
|
|
817
|
+
matches = matches.filter(
|
|
818
|
+
msg =>
|
|
819
|
+
msg.timestamp >= query.dateRange.from &&
|
|
820
|
+
msg.timestamp <= query.dateRange.to
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (query.tags && query.tags.length > 0) {
|
|
825
|
+
const hasMatchingTag = query.tags.some((tag: string) =>
|
|
826
|
+
session.tags.includes(tag)
|
|
827
|
+
);
|
|
828
|
+
if (!hasMatchingTag) {
|
|
829
|
+
matches = [];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (query.model && session.model !== query.model) {
|
|
834
|
+
matches = [];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return matches;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private calculateSearchScore(matches: ChatMessage[], query: any): number {
|
|
841
|
+
let score = matches.length;
|
|
842
|
+
|
|
843
|
+
if (query.text) {
|
|
844
|
+
// Boost score based on exact matches
|
|
845
|
+
const exactMatches = matches.filter(msg =>
|
|
846
|
+
msg.content.toLowerCase().includes(query.text.toLowerCase())
|
|
847
|
+
).length;
|
|
848
|
+
score += exactMatches * 2;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return score;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private sessionToMarkdown(session: EnhancedChatSession): string {
|
|
855
|
+
let markdown = `# Conversation: ${session.id}\n\n`;
|
|
856
|
+
markdown += `**Model:** ${session.model}\n`;
|
|
857
|
+
markdown += `**Created:** ${session.created.toLocaleString()}\n`;
|
|
858
|
+
markdown += `**Last Updated:** ${session.updated.toLocaleString()}\n`;
|
|
859
|
+
markdown += `**Messages:** ${session.history.length}\n`;
|
|
860
|
+
|
|
861
|
+
if (session.tags.length > 0) {
|
|
862
|
+
markdown += `**Tags:** ${session.tags.join(', ')}\n`;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
markdown += '\n---\n\n';
|
|
866
|
+
|
|
867
|
+
session.history.forEach((msg, index) => {
|
|
868
|
+
const role = msg.role === 'user' ? '🧑 **You**' : '🤖 **Assistant**';
|
|
869
|
+
markdown += `## Message ${index + 1}\n\n`;
|
|
870
|
+
markdown += `${role} - *${msg.timestamp.toLocaleString()}*\n\n`;
|
|
871
|
+
markdown += `${msg.content}\n\n`;
|
|
872
|
+
markdown += '---\n\n';
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
return markdown;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private sessionToCsv(session: EnhancedChatSession): string {
|
|
879
|
+
const headers = ['Index', 'Role', 'Content', 'Timestamp'];
|
|
880
|
+
const rows = [headers];
|
|
881
|
+
|
|
882
|
+
session.history.forEach((msg, index) => {
|
|
883
|
+
rows.push([
|
|
884
|
+
(index + 1).toString(),
|
|
885
|
+
msg.role,
|
|
886
|
+
`"${msg.content.replace(/"/g, '""')}"`, // Escape quotes
|
|
887
|
+
msg.timestamp.toISOString(),
|
|
888
|
+
]);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Cleanup resources
|
|
896
|
+
*/
|
|
897
|
+
destroy(): void {
|
|
898
|
+
if (this.sessionCleanupTimer) {
|
|
899
|
+
clearInterval(this.sessionCleanupTimer);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
this.activeSessions.clear();
|
|
903
|
+
this.removeAllListeners();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export default ConversationManager;
|