agentgui 1.0.451 → 1.0.453

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/.prd ADDED
File without changes
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
  };
@@ -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
- return agent.run(prompt, cwd, config);
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
  /**
@@ -9,7 +9,7 @@ export function register(router, deps) {
9
9
  const { queries, wsOptimizer, modelDownloadState, ensureModelsDownloaded,
10
10
  broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
11
11
  startGeminiOAuth, exchangeGeminiOAuthCode, geminiOAuthState,
12
- STARTUP_CWD, activeScripts } = deps;
12
+ STARTUP_CWD, activeScripts, voiceCacheManager } = deps;
13
13
 
14
14
  router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
15
15
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.451",
3
+ "version": "1.0.453",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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 = `Your output will be spoken aloud by a text-to-speech system. Write ONLY plain conversational sentences that sound natural when read aloud. Never use markdown, bold, italics, headers, bullet points, numbered lists, tables, or any formatting. Never use colons to introduce lists or options. Never use labels like "Option A" or "1." followed by a title. Instead of listing options, describe them conversationally in flowing sentences. For example, instead of "**Option 1**: Do X" say "One approach would be to do X." Keep sentences short and simple. Use transition words like "also", "another option", "or alternatively" to connect ideas. Avoid technical notations - describe concepts naturally without spelling out file extensions. Keep file mentions minimal or omit them entirely when possible. If you must mention a file, use natural phrasing like "the server file" or "the main script" rather than technical names. At the end, provide a VERY brief summary in 1-2 sentences maximum. Write as if you are speaking to someone in a casual conversation.`;
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) => {