@mmmbuto/nexuscrew 0.1.0-beta.1 → 0.2.1
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 +67 -120
- package/bin/nexuscrew.js +468 -264
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/{index-C7bAndew.js → index-BG1N7bSL.js} +1710 -1702
- package/frontend/dist/assets/index-CiAtinNP.css +1 -0
- package/frontend/dist/favicon.svg +20 -0
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/icon-512.png +0 -0
- package/frontend/dist/index.html +11 -3
- package/frontend/dist/site.webmanifest +29 -0
- package/lib/config/hosts.js +67 -0
- package/lib/config/manager.js +379 -0
- package/lib/config/models.js +408 -0
- package/lib/server/db/adapter.js +274 -0
- package/lib/server/db/drivers/sql-js.js +75 -0
- package/lib/server/db/migrate.js +174 -0
- package/lib/server/db/migrations/001_base_schema.sql +70 -0
- package/lib/server/middleware/auth.js +134 -0
- package/lib/server/middleware/rate-limit.js +63 -0
- package/lib/server/models/User.js +128 -0
- package/lib/server/routes/auth.js +168 -0
- package/lib/server/routes/hosts.js +11 -20
- package/lib/server/routes/keys.js +28 -0
- package/lib/server/routes/models.js +59 -4
- package/lib/server/routes/runtimes.js +34 -0
- package/lib/server/routes/send.js +76 -12
- package/lib/server/routes/sessions.js +39 -10
- package/lib/server/routes/speech.js +46 -0
- package/lib/server/routes/status.js +8 -6
- package/lib/server/routes/upload.js +135 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspaces.js +101 -0
- package/lib/server/server.js +66 -33
- package/lib/server/services/attachment-manager.js +57 -0
- package/lib/server/services/context-bridge.js +425 -0
- package/lib/server/services/runtime-manager.js +462 -0
- package/lib/server/services/speech-manager.js +76 -0
- package/lib/server/services/summary-generator.js +309 -0
- package/lib/server/services/workspace-manager.js +79 -0
- package/lib/services/engine-discovery.js +198 -13
- package/lib/services/log-watcher.js +40 -22
- package/lib/services/remote-pane-watcher.js +155 -0
- package/lib/services/session-store.js +60 -64
- package/lib/services/tmux-manager.js +127 -116
- package/lib/setup/postinstall.js +38 -0
- package/lib/utils/paths.js +124 -0
- package/lib/utils/termux.js +182 -0
- package/package.json +8 -4
- package/frontend/dist/assets/index-OENqI1_9.css +0 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextBridge - Unified context management with token optimization
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Token-aware context truncation
|
|
6
|
+
* - Engine-specific context compression
|
|
7
|
+
* - Auto-summary triggering
|
|
8
|
+
* - Unified bridging logic for all engines
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { prepare } = require('../db/adapter');
|
|
12
|
+
const SummaryGenerator = require('./summary-generator');
|
|
13
|
+
|
|
14
|
+
// Token estimation constants (approximate)
|
|
15
|
+
const CHARS_PER_TOKEN = 4; // GPT/Claude average
|
|
16
|
+
const DEFAULT_MAX_TOKENS = 4000; // Context budget for bridging
|
|
17
|
+
const SUMMARY_TRIGGER_THRESHOLD = 15; // Messages before auto-summary
|
|
18
|
+
|
|
19
|
+
// Engine-specific context limits
|
|
20
|
+
const ENGINE_LIMITS = {
|
|
21
|
+
'claude': { maxTokens: 4000, preferSummary: true },
|
|
22
|
+
'codex': { maxTokens: 3000, preferSummary: true, codeOnly: true },
|
|
23
|
+
'deepseek': { maxTokens: 3000, preferSummary: true },
|
|
24
|
+
'gemini': { maxTokens: 6000, preferSummary: false }, // Gemini has large context
|
|
25
|
+
'qwen': { maxTokens: 6000, preferSummary: false } // Qwen Coder large context
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class ContextBridge {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.summaryGenerator = new SummaryGenerator();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Estimate token count for text
|
|
35
|
+
* @param {string} text - Text to estimate
|
|
36
|
+
* @returns {number} Estimated token count
|
|
37
|
+
*/
|
|
38
|
+
estimateTokens(text) {
|
|
39
|
+
if (!text) return 0;
|
|
40
|
+
// Simple estimation: ~4 chars per token for English/code
|
|
41
|
+
// More accurate would use tiktoken, but this is faster
|
|
42
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get engine-specific configuration
|
|
47
|
+
* @param {string} engine - Engine name
|
|
48
|
+
* @returns {Object} Engine config
|
|
49
|
+
*/
|
|
50
|
+
getEngineConfig(engine) {
|
|
51
|
+
return ENGINE_LIMITS[engine] || ENGINE_LIMITS['claude'];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build optimized context for engine switch
|
|
56
|
+
* @param {Object} params
|
|
57
|
+
* @param {string} params.conversationId - Stable conversation ID (cross-engine)
|
|
58
|
+
* @param {string} params.sessionId - Legacy session ID (fallback)
|
|
59
|
+
* @param {string} params.fromEngine - Previous engine
|
|
60
|
+
* @param {string} params.toEngine - Target engine
|
|
61
|
+
* @param {string} params.userMessage - Current user message
|
|
62
|
+
* @returns {Object} { prompt, isEngineBridge, contextTokens }
|
|
63
|
+
*/
|
|
64
|
+
async buildContext({ conversationId, sessionId, fromEngine, toEngine, userMessage }) {
|
|
65
|
+
const config = this.getEngineConfig(toEngine);
|
|
66
|
+
const isEngineBridge = fromEngine && fromEngine !== toEngine;
|
|
67
|
+
const convoId = conversationId || sessionId; // backward compat
|
|
68
|
+
|
|
69
|
+
// Reserve tokens for user message
|
|
70
|
+
const userTokens = this.estimateTokens(userMessage);
|
|
71
|
+
const availableTokens = Math.max(0, config.maxTokens - userTokens - 200); // 200 token buffer, never negative
|
|
72
|
+
|
|
73
|
+
let contextText = '';
|
|
74
|
+
let contextTokens = 0;
|
|
75
|
+
let contextSource = 'none';
|
|
76
|
+
|
|
77
|
+
// For engine bridge, use structured handoff template
|
|
78
|
+
if (isEngineBridge) {
|
|
79
|
+
const handoffContext = this.buildEngineHandoffContext(convoId, fromEngine, toEngine, availableTokens, config);
|
|
80
|
+
if (handoffContext.text) {
|
|
81
|
+
contextText = handoffContext.text;
|
|
82
|
+
contextTokens = handoffContext.tokens;
|
|
83
|
+
contextSource = handoffContext.source;
|
|
84
|
+
}
|
|
85
|
+
// If handoff couldn't fit, fall back to history-only context
|
|
86
|
+
if (!contextText && availableTokens > 200) {
|
|
87
|
+
const historyContext = this.buildTokenAwareHistory(convoId, availableTokens, config);
|
|
88
|
+
if (historyContext.text) {
|
|
89
|
+
contextText = historyContext.text;
|
|
90
|
+
contextTokens = historyContext.tokens;
|
|
91
|
+
contextSource = 'history_fallback';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Try summary first (most efficient)
|
|
96
|
+
if (config.preferSummary) {
|
|
97
|
+
const summaryContext = this.summaryGenerator.getBridgeContext(convoId);
|
|
98
|
+
if (summaryContext) {
|
|
99
|
+
const summaryTokens = this.estimateTokens(summaryContext);
|
|
100
|
+
if (summaryTokens <= availableTokens) {
|
|
101
|
+
contextText = summaryContext;
|
|
102
|
+
contextTokens = summaryTokens;
|
|
103
|
+
contextSource = 'summary';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fallback to token-aware message history
|
|
109
|
+
if (!contextText && availableTokens > 200) {
|
|
110
|
+
const historyContext = this.buildTokenAwareHistory(convoId, availableTokens, config);
|
|
111
|
+
if (historyContext.text) {
|
|
112
|
+
contextText = historyContext.text;
|
|
113
|
+
contextTokens = historyContext.tokens;
|
|
114
|
+
contextSource = 'history';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Build final prompt
|
|
120
|
+
let prompt = userMessage;
|
|
121
|
+
|
|
122
|
+
if (availableTokens === 0) {
|
|
123
|
+
console.log(`[ContextBridge] Budget exhausted before context; sending raw message (engine=${toEngine})`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (contextText) {
|
|
127
|
+
prompt = `${contextText}\n\n${userMessage}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(`[ContextBridge] Built context: ${contextTokens} tokens from ${contextSource}, bridge: ${isEngineBridge}, avail=${availableTokens}, total=${contextTokens + userTokens}`);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
prompt,
|
|
134
|
+
isEngineBridge,
|
|
135
|
+
contextTokens,
|
|
136
|
+
contextSource,
|
|
137
|
+
totalTokens: contextTokens + userTokens
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build structured context for engine handoff
|
|
143
|
+
* Uses a clear template that helps the new engine understand previous context
|
|
144
|
+
* @param {string} conversationId
|
|
145
|
+
* @param {string} fromEngine
|
|
146
|
+
* @param {string} toEngine
|
|
147
|
+
* @param {number} maxTokens
|
|
148
|
+
* @param {Object} config
|
|
149
|
+
* @returns {Object} { text, tokens, source }
|
|
150
|
+
*/
|
|
151
|
+
buildEngineHandoffContext(conversationId, fromEngine, toEngine, maxTokens, config = {}) {
|
|
152
|
+
const engineNames = {
|
|
153
|
+
'claude': 'Claude Code (Anthropic)',
|
|
154
|
+
'codex': 'Codex (OpenAI)',
|
|
155
|
+
'gemini': 'Gemini (Google)',
|
|
156
|
+
'deepseek': 'DeepSeek',
|
|
157
|
+
'qwen': 'Qwen Code (Alibaba)'
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const fromName = engineNames[fromEngine] || fromEngine;
|
|
161
|
+
const toName = engineNames[toEngine] || toEngine;
|
|
162
|
+
|
|
163
|
+
// Get summary if available
|
|
164
|
+
const summary = this.summaryGenerator.getSummary(conversationId);
|
|
165
|
+
|
|
166
|
+
// Get recent messages (last 5) and message count
|
|
167
|
+
const stmt = prepare('SELECT role, content, engine FROM messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT 5');
|
|
168
|
+
const messages = stmt.all(conversationId);
|
|
169
|
+
|
|
170
|
+
const countStmt = prepare('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?');
|
|
171
|
+
const { count: messageCount } = countStmt.get(conversationId);
|
|
172
|
+
|
|
173
|
+
// Build structured template
|
|
174
|
+
const sections = [];
|
|
175
|
+
|
|
176
|
+
// Header
|
|
177
|
+
sections.push(`<previous_session_context engine="${fromEngine}" total_messages="${messageCount}">`);
|
|
178
|
+
sections.push(`This conversation was previously handled by ${fromName}.`);
|
|
179
|
+
sections.push(`You are now continuing as ${toName}.`);
|
|
180
|
+
sections.push('');
|
|
181
|
+
|
|
182
|
+
// Summary section (if available)
|
|
183
|
+
if (summary && summary.summary_short) {
|
|
184
|
+
sections.push('## Summary');
|
|
185
|
+
sections.push(summary.summary_short);
|
|
186
|
+
sections.push('');
|
|
187
|
+
|
|
188
|
+
// Key decisions
|
|
189
|
+
if (summary.key_decisions && summary.key_decisions.length > 0) {
|
|
190
|
+
sections.push('## Key Decisions');
|
|
191
|
+
summary.key_decisions.slice(0, 5).forEach(d => sections.push(`- ${d}`));
|
|
192
|
+
sections.push('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Files modified
|
|
196
|
+
if (summary.files_modified && summary.files_modified.length > 0) {
|
|
197
|
+
sections.push('## Files Modified');
|
|
198
|
+
summary.files_modified.slice(0, 10).forEach(f => sections.push(`- ${f}`));
|
|
199
|
+
sections.push('');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Recent messages (always include for continuity)
|
|
204
|
+
if (messages.length > 0) {
|
|
205
|
+
sections.push('## Recent Messages');
|
|
206
|
+
for (const msg of messages) {
|
|
207
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant';
|
|
208
|
+
const engine = msg.engine ? ` [${msg.engine}]` : '';
|
|
209
|
+
// Truncate long messages
|
|
210
|
+
let content = msg.content || '';
|
|
211
|
+
if (content.length > 500) {
|
|
212
|
+
content = content.substring(0, 500) + '...';
|
|
213
|
+
}
|
|
214
|
+
sections.push(`${role}${engine}: ${content}`);
|
|
215
|
+
sections.push('');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
sections.push('</previous_session_context>');
|
|
220
|
+
sections.push('');
|
|
221
|
+
sections.push('Continue assisting with the following request:');
|
|
222
|
+
|
|
223
|
+
const text = sections.join('\n');
|
|
224
|
+
const tokens = this.estimateTokens(text);
|
|
225
|
+
|
|
226
|
+
// Check token budget
|
|
227
|
+
if (tokens > maxTokens) {
|
|
228
|
+
// Fallback to simpler context if too long
|
|
229
|
+
console.log(`[ContextBridge] Handoff template too long (${tokens} > ${maxTokens}), using fallback`);
|
|
230
|
+
const fallback = this.buildTokenAwareHistory(conversationId, maxTokens, config);
|
|
231
|
+
return {
|
|
232
|
+
text: fallback.text,
|
|
233
|
+
tokens: fallback.tokens,
|
|
234
|
+
source: 'handoff_fallback_history'
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
text,
|
|
240
|
+
tokens,
|
|
241
|
+
source: summary ? 'handoff+summary' : 'handoff+history'
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Build token-aware history context
|
|
247
|
+
* @param {string} conversationId - Conversation ID
|
|
248
|
+
* @param {number} maxTokens - Token budget
|
|
249
|
+
* @param {Object} config - Engine config
|
|
250
|
+
* @returns {Object} { text, tokens, messageCount }
|
|
251
|
+
*/
|
|
252
|
+
buildTokenAwareHistory(conversationId, maxTokens, config = {}) {
|
|
253
|
+
// Get more messages than we need, we'll trim
|
|
254
|
+
const stmt = prepare('SELECT role, content, engine FROM messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT 20');
|
|
255
|
+
const messages = stmt.all(conversationId);
|
|
256
|
+
|
|
257
|
+
if (messages.length === 0) {
|
|
258
|
+
return { text: '', tokens: 0, messageCount: 0 };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lines = [];
|
|
262
|
+
let tokenCount = 0;
|
|
263
|
+
let includedCount = 0;
|
|
264
|
+
|
|
265
|
+
// Process from newest to oldest
|
|
266
|
+
for (let i = 0; i < messages.length; i++) {
|
|
267
|
+
const msg = messages[i];
|
|
268
|
+
|
|
269
|
+
// For code-focused engines, compress assistant responses to code only
|
|
270
|
+
// BUT always keep user messages for context continuity
|
|
271
|
+
let content = msg.content;
|
|
272
|
+
if (config.codeOnly && msg.role === 'assistant') {
|
|
273
|
+
const codeContent = this.extractCodeContent(content);
|
|
274
|
+
// Only use code-only if there's actual code, otherwise keep truncated original
|
|
275
|
+
content = codeContent || (content.length > 500 ? content.substring(0, 500) + '...' : content);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Truncate long messages
|
|
279
|
+
if (content.length > 2000) {
|
|
280
|
+
content = content.substring(0, 2000) + '...';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant';
|
|
284
|
+
const engineTag = msg.engine ? ` [${msg.engine}]` : '';
|
|
285
|
+
const line = `${role}${engineTag}: ${content}`;
|
|
286
|
+
|
|
287
|
+
const lineTokens = this.estimateTokens(line);
|
|
288
|
+
|
|
289
|
+
// Check if adding this would exceed budget
|
|
290
|
+
if (tokenCount + lineTokens > maxTokens) {
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
lines.unshift(line); // Add to beginning (chronological)
|
|
295
|
+
tokenCount += lineTokens;
|
|
296
|
+
includedCount++;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (lines.length === 0) {
|
|
300
|
+
return { text: '', tokens: 0, messageCount: 0 };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const text = `[Context from recent messages]\n${lines.join('\n\n')}`;
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
text,
|
|
307
|
+
tokens: tokenCount,
|
|
308
|
+
messageCount: includedCount
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Extract code blocks and technical content from text
|
|
314
|
+
* @param {string} text - Full text
|
|
315
|
+
* @returns {string} Code-focused content
|
|
316
|
+
*/
|
|
317
|
+
extractCodeContent(text) {
|
|
318
|
+
if (!text) return '';
|
|
319
|
+
|
|
320
|
+
// Extract code blocks
|
|
321
|
+
const codeBlocks = [];
|
|
322
|
+
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
323
|
+
let match;
|
|
324
|
+
|
|
325
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
326
|
+
codeBlocks.push(match[0]);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (codeBlocks.length > 0) {
|
|
330
|
+
return codeBlocks.join('\n\n');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// If no code blocks, check if text contains technical content
|
|
334
|
+
const technicalPatterns = [
|
|
335
|
+
/function\s+\w+/,
|
|
336
|
+
/const\s+\w+\s*=/,
|
|
337
|
+
/class\s+\w+/,
|
|
338
|
+
/import\s+.*from/,
|
|
339
|
+
/require\s*\(/,
|
|
340
|
+
/\/\/|\/\*|\*\//,
|
|
341
|
+
/\.(js|ts|py|java|cpp|go|rs)\b/,
|
|
342
|
+
/npm|git|docker|curl/
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
for (const pattern of technicalPatterns) {
|
|
346
|
+
if (pattern.test(text)) {
|
|
347
|
+
return text;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return ''; // Not code-relevant
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check if auto-summary should be triggered
|
|
356
|
+
* @param {string} conversationId - Conversation ID
|
|
357
|
+
* @param {boolean} isEngineBridge - Was this an engine switch
|
|
358
|
+
* @returns {boolean} Should generate summary
|
|
359
|
+
*/
|
|
360
|
+
shouldTriggerSummary(conversationId, isEngineBridge = false) {
|
|
361
|
+
// Always trigger on engine bridge
|
|
362
|
+
if (isEngineBridge) return true;
|
|
363
|
+
|
|
364
|
+
const countStmt = prepare('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?');
|
|
365
|
+
const { count: messageCount } = countStmt.get(conversationId);
|
|
366
|
+
|
|
367
|
+
// Trigger every 10 messages after threshold
|
|
368
|
+
if (messageCount >= SUMMARY_TRIGGER_THRESHOLD && messageCount % 10 === 0) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if we have a stale summary (older than 20 messages)
|
|
373
|
+
const existingSummary = this.summaryGenerator.getSummary(conversationId);
|
|
374
|
+
if (!existingSummary && messageCount > SUMMARY_TRIGGER_THRESHOLD) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Trigger summary generation (async, non-blocking)
|
|
383
|
+
* @param {string} conversationId - Conversation ID
|
|
384
|
+
* @param {string} logPrefix - Log prefix for debugging
|
|
385
|
+
*/
|
|
386
|
+
triggerSummaryGeneration(conversationId, logPrefix = '[ContextBridge]') {
|
|
387
|
+
const stmt = prepare('SELECT role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT 40');
|
|
388
|
+
const messages = stmt.all(conversationId);
|
|
389
|
+
|
|
390
|
+
this.summaryGenerator.generateAndSave(conversationId, messages)
|
|
391
|
+
.then(summary => {
|
|
392
|
+
if (summary) {
|
|
393
|
+
console.log(`${logPrefix} Summary updated: ${summary.summary_short?.substring(0, 50)}...`);
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
.catch(err => {
|
|
397
|
+
console.warn(`${logPrefix} Summary generation failed:`, err.message);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get context stats for debugging
|
|
403
|
+
* @param {string} conversationId - Conversation ID
|
|
404
|
+
* @returns {Object} Stats
|
|
405
|
+
*/
|
|
406
|
+
getContextStats(conversationId) {
|
|
407
|
+
const countStmt = prepare('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?');
|
|
408
|
+
const { count: messageCount } = countStmt.get(conversationId);
|
|
409
|
+
|
|
410
|
+
const lastMsgStmt = prepare('SELECT engine FROM messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT 1');
|
|
411
|
+
const lastMsg = lastMsgStmt.get(conversationId);
|
|
412
|
+
const lastEngine = lastMsg?.engine || null;
|
|
413
|
+
|
|
414
|
+
const hasSummary = !!this.summaryGenerator.getSummary(conversationId);
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
messageCount,
|
|
418
|
+
lastEngine,
|
|
419
|
+
hasSummary,
|
|
420
|
+
summaryThreshold: SUMMARY_TRIGGER_THRESHOLD
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
module.exports = new ContextBridge();
|