agentgui 1.0.450 → 1.0.452
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/database.js +69 -0
- package/lib/claude-runner.js +12 -1
- package/lib/ws-handlers-util.js +21 -0
- package/package.json +1 -1
- package/server.js +36 -2
- package/static/js/voice.js +3 -7
package/database.js
CHANGED
|
@@ -151,6 +151,20 @@ function initSchema() {
|
|
|
151
151
|
CREATE INDEX IF NOT EXISTS idx_chunks_conv_created ON chunks(conversationId, created_at);
|
|
152
152
|
CREATE INDEX IF NOT EXISTS idx_chunks_sess_created ON chunks(sessionId, created_at);
|
|
153
153
|
|
|
154
|
+
CREATE TABLE IF NOT EXISTS voice_cache (
|
|
155
|
+
id TEXT PRIMARY KEY,
|
|
156
|
+
conversationId TEXT NOT NULL,
|
|
157
|
+
text TEXT NOT NULL,
|
|
158
|
+
audioBlob BLOB,
|
|
159
|
+
byteSize INTEGER NOT NULL,
|
|
160
|
+
created_at INTEGER NOT NULL,
|
|
161
|
+
expires_at INTEGER NOT NULL,
|
|
162
|
+
FOREIGN KEY (conversationId) REFERENCES conversations(id)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_voice_cache_conv ON voice_cache(conversationId);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_voice_cache_expires ON voice_cache(expires_at);
|
|
167
|
+
|
|
154
168
|
`);
|
|
155
169
|
}
|
|
156
170
|
|
|
@@ -1474,6 +1488,61 @@ export const queries = {
|
|
|
1474
1488
|
stmt.run('paused', errorMessage, Date.now(), downloadId);
|
|
1475
1489
|
},
|
|
1476
1490
|
|
|
1491
|
+
saveVoiceCache(conversationId, text, audioBlob, ttlMs = 3600000) {
|
|
1492
|
+
const id = generateId('vcache');
|
|
1493
|
+
const now = Date.now();
|
|
1494
|
+
const expiresAt = now + ttlMs;
|
|
1495
|
+
const byteSize = audioBlob ? Buffer.byteLength(audioBlob) : 0;
|
|
1496
|
+
const stmt = prep(`
|
|
1497
|
+
INSERT INTO voice_cache (id, conversationId, text, audioBlob, byteSize, created_at, expires_at)
|
|
1498
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1499
|
+
`);
|
|
1500
|
+
stmt.run(id, conversationId, text, audioBlob || null, byteSize, now, expiresAt);
|
|
1501
|
+
return { id, conversationId, text, byteSize, created_at: now, expires_at: expiresAt };
|
|
1502
|
+
},
|
|
1503
|
+
|
|
1504
|
+
getVoiceCache(conversationId, text) {
|
|
1505
|
+
const now = Date.now();
|
|
1506
|
+
const stmt = prep(`
|
|
1507
|
+
SELECT id, conversationId, text, audioBlob, byteSize, created_at, expires_at
|
|
1508
|
+
FROM voice_cache
|
|
1509
|
+
WHERE conversationId = ? AND text = ? AND expires_at > ?
|
|
1510
|
+
LIMIT 1
|
|
1511
|
+
`);
|
|
1512
|
+
return stmt.get(conversationId, text, now) || null;
|
|
1513
|
+
},
|
|
1514
|
+
|
|
1515
|
+
cleanExpiredVoiceCache() {
|
|
1516
|
+
const now = Date.now();
|
|
1517
|
+
const stmt = prep('DELETE FROM voice_cache WHERE expires_at <= ?');
|
|
1518
|
+
return stmt.run(now).changes;
|
|
1519
|
+
},
|
|
1520
|
+
|
|
1521
|
+
getVoiceCacheSize(conversationId) {
|
|
1522
|
+
const now = Date.now();
|
|
1523
|
+
const stmt = prep(`
|
|
1524
|
+
SELECT COALESCE(SUM(byteSize), 0) as totalSize
|
|
1525
|
+
FROM voice_cache
|
|
1526
|
+
WHERE conversationId = ? AND expires_at > ?
|
|
1527
|
+
`);
|
|
1528
|
+
return stmt.get(conversationId, now).totalSize || 0;
|
|
1529
|
+
},
|
|
1530
|
+
|
|
1531
|
+
deleteOldestVoiceCache(conversationId, neededBytes) {
|
|
1532
|
+
const stmt = prep(`
|
|
1533
|
+
SELECT id FROM voice_cache
|
|
1534
|
+
WHERE conversationId = ?
|
|
1535
|
+
ORDER BY created_at ASC
|
|
1536
|
+
LIMIT (SELECT COUNT(*) FROM voice_cache WHERE conversationId = ? AND byteSize > ?)
|
|
1537
|
+
`);
|
|
1538
|
+
const oldest = stmt.all(conversationId, conversationId, neededBytes);
|
|
1539
|
+
const deleteStmt = prep('DELETE FROM voice_cache WHERE id = ?');
|
|
1540
|
+
for (const row of oldest) {
|
|
1541
|
+
deleteStmt.run(row.id);
|
|
1542
|
+
}
|
|
1543
|
+
return oldest.length;
|
|
1544
|
+
},
|
|
1545
|
+
|
|
1477
1546
|
// ============ ACP-COMPATIBLE QUERIES ============
|
|
1478
1547
|
...createACPQueries(db, prep)
|
|
1479
1548
|
};
|
package/lib/claude-runner.js
CHANGED
|
@@ -1147,7 +1147,18 @@ export async function runClaudeWithStreaming(prompt, cwd, agentId = 'claude-code
|
|
|
1147
1147
|
throw new Error(`Unknown agent: ${agentId}. Registered agents: ${registry.list().map(a => a.id).join(', ')}`);
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
|
-
|
|
1150
|
+
const enhancedConfig = { ...config };
|
|
1151
|
+
if (!enhancedConfig.systemPrompt) {
|
|
1152
|
+
enhancedConfig.systemPrompt = '';
|
|
1153
|
+
}
|
|
1154
|
+
if (agentId && agentId !== 'claude-code') {
|
|
1155
|
+
const agentPrefix = `use ${agentId} subagent to. `;
|
|
1156
|
+
if (!enhancedConfig.systemPrompt.includes(agentPrefix)) {
|
|
1157
|
+
enhancedConfig.systemPrompt = agentPrefix + enhancedConfig.systemPrompt;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return agent.run(prompt, cwd, enhancedConfig);
|
|
1151
1162
|
}
|
|
1152
1163
|
|
|
1153
1164
|
/**
|
package/lib/ws-handlers-util.js
CHANGED
|
@@ -231,4 +231,25 @@ export function register(router, deps) {
|
|
|
231
231
|
try { process.kill(-entry.process.pid, 'SIGTERM'); } catch { try { entry.process.kill('SIGTERM'); } catch {} }
|
|
232
232
|
return { ok: true };
|
|
233
233
|
});
|
|
234
|
+
|
|
235
|
+
router.handle('voice.cache', async (p) => {
|
|
236
|
+
const { conversationId, text } = p;
|
|
237
|
+
if (!conversationId || !text) err(400, 'Missing conversationId or text');
|
|
238
|
+
try {
|
|
239
|
+
const cached = queries.getVoiceCache(conversationId, text);
|
|
240
|
+
if (cached && cached.audioBlob) {
|
|
241
|
+
return { ok: true, cached: true, byteSize: cached.byteSize };
|
|
242
|
+
}
|
|
243
|
+
return { ok: true, cached: false };
|
|
244
|
+
} catch (e) { err(500, e.message); }
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
router.handle('voice.generate', async (p) => {
|
|
248
|
+
const { conversationId, text } = p;
|
|
249
|
+
if (!conversationId || !text) err(400, 'Missing conversationId or text');
|
|
250
|
+
try {
|
|
251
|
+
const result = await voiceCacheManager.getOrGenerateCache(conversationId, text);
|
|
252
|
+
return { ok: true, byteSize: result.byteSize, cached: true };
|
|
253
|
+
} catch (e) { err(500, e.message); }
|
|
254
|
+
});
|
|
234
255
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -42,6 +42,40 @@ process.on('beforeExit', (code) => { console.log('[PROCESS] beforeExit with code
|
|
|
42
42
|
process.on('exit', (code) => { console.log('[PROCESS] exit with code:', code); });
|
|
43
43
|
|
|
44
44
|
const ttsTextAccumulators = new Map();
|
|
45
|
+
const voiceCacheManager = {
|
|
46
|
+
generating: new Map(),
|
|
47
|
+
maxCacheSize: 10 * 1024 * 1024,
|
|
48
|
+
async getOrGenerateCache(conversationId, text) {
|
|
49
|
+
const cacheKey = `${conversationId}:${text}`;
|
|
50
|
+
if (this.generating.has(cacheKey)) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const checkInterval = setInterval(() => {
|
|
53
|
+
const cached = queries.getVoiceCache(conversationId, text);
|
|
54
|
+
if (cached) {
|
|
55
|
+
clearInterval(checkInterval);
|
|
56
|
+
resolve(cached);
|
|
57
|
+
}
|
|
58
|
+
}, 50);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const cached = queries.getVoiceCache(conversationId, text);
|
|
62
|
+
if (cached) return cached;
|
|
63
|
+
this.generating.set(cacheKey, true);
|
|
64
|
+
try {
|
|
65
|
+
const speech = await getSpeech();
|
|
66
|
+
const audioBlob = await speech.ttsSync(text, 'en-US');
|
|
67
|
+
const saved = queries.saveVoiceCache(conversationId, text, audioBlob);
|
|
68
|
+
const totalSize = queries.getVoiceCacheSize(conversationId);
|
|
69
|
+
if (totalSize > this.maxCacheSize) {
|
|
70
|
+
const needed = totalSize - this.maxCacheSize;
|
|
71
|
+
queries.deleteOldestVoiceCache(conversationId, needed);
|
|
72
|
+
}
|
|
73
|
+
return saved;
|
|
74
|
+
} finally {
|
|
75
|
+
this.generating.delete(cacheKey);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
45
79
|
|
|
46
80
|
let speechModule = null;
|
|
47
81
|
async function getSpeech() {
|
|
@@ -233,7 +267,7 @@ function pushTTSAudio(cacheKey, wav, conversationId, sessionId, voiceId) {
|
|
|
233
267
|
}
|
|
234
268
|
|
|
235
269
|
|
|
236
|
-
const SYSTEM_PROMPT = `
|
|
270
|
+
const SYSTEM_PROMPT = `Plain text. Spoken aloud. Conversational. No markdown, formatting, bullets, or lists. Short sentences. Technical facts only.`;
|
|
237
271
|
|
|
238
272
|
const activeExecutions = new Map();
|
|
239
273
|
const activeScripts = new Map();
|
|
@@ -3842,7 +3876,7 @@ registerUtilHandlers(wsRouter, {
|
|
|
3842
3876
|
broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
|
|
3843
3877
|
startGeminiOAuth, exchangeGeminiOAuthCode,
|
|
3844
3878
|
geminiOAuthState: () => geminiOAuthState,
|
|
3845
|
-
STARTUP_CWD, activeScripts
|
|
3879
|
+
STARTUP_CWD, activeScripts, voiceCacheManager
|
|
3846
3880
|
});
|
|
3847
3881
|
|
|
3848
3882
|
wsRouter.onLegacy((data, ws) => {
|
package/static/js/voice.js
CHANGED
|
@@ -578,9 +578,9 @@
|
|
|
578
578
|
}
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
function stripHtml(text) {
|
|
582
|
-
return text.replace(/<[^>]*>/g, '').replace(/[ \t]+/g, ' ').trim();
|
|
583
|
-
}
|
|
581
|
+
function stripHtml(text) {
|
|
582
|
+
return text.replace(/<[^>]*>/g, '').replace(/[ \t]+/g, ' ').trim();
|
|
583
|
+
}
|
|
584
584
|
|
|
585
585
|
function addVoiceBlock(text, isUser) {
|
|
586
586
|
var container = document.getElementById('voiceMessages');
|
|
@@ -742,8 +742,6 @@
|
|
|
742
742
|
}
|
|
743
743
|
} else if (block.type === 'result') {
|
|
744
744
|
_voiceBreakNext = true;
|
|
745
|
-
} else {
|
|
746
|
-
_voiceBreakNext = true;
|
|
747
745
|
}
|
|
748
746
|
}
|
|
749
747
|
|
|
@@ -780,8 +778,6 @@
|
|
|
780
778
|
hasContent = true;
|
|
781
779
|
} else if (block.type === 'result') {
|
|
782
780
|
_voiceBreakNext = true;
|
|
783
|
-
} else {
|
|
784
|
-
_voiceBreakNext = true;
|
|
785
781
|
}
|
|
786
782
|
});
|
|
787
783
|
if (!hasContent) showVoiceEmpty(container);
|