agentgui 1.0.181 → 1.0.182

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 CHANGED
@@ -354,6 +354,13 @@ export const queries = {
354
354
  return stmt.all();
355
355
  },
356
356
 
357
+ getResumableConversations() {
358
+ const stmt = prep(
359
+ "SELECT id, title, claudeSessionId, agentType, workingDirectory FROM conversations WHERE isStreaming = 1 AND claudeSessionId IS NOT NULL AND claudeSessionId != ''"
360
+ );
361
+ return stmt.all();
362
+ },
363
+
357
364
  clearAllStreamingFlags() {
358
365
  const stmt = prep('UPDATE conversations SET isStreaming = 0 WHERE isStreaming = 1');
359
366
  return stmt.run().changes;
package/lib/speech.js CHANGED
@@ -105,6 +105,8 @@ let transformersModule = null;
105
105
  let sttPipeline = null;
106
106
  let sttLoading = false;
107
107
  let sttLoadError = null;
108
+ let sttLoadErrorTime = 0;
109
+ const STT_RETRY_MS = 30000;
108
110
  const SAMPLE_RATE_STT = 16000;
109
111
 
110
112
  const TTS_CACHE_MAX_BYTES = 10 * 1024 * 1024;
@@ -159,10 +161,10 @@ async function decodeAudioFile(filePath) {
159
161
 
160
162
  async function getSTT() {
161
163
  if (sttPipeline) return sttPipeline;
162
- if (sttLoadError) throw sttLoadError;
164
+ if (sttLoadError && (Date.now() - sttLoadErrorTime < STT_RETRY_MS)) throw sttLoadError;
163
165
  if (sttLoading) {
164
166
  while (sttLoading) await new Promise(r => setTimeout(r, 100));
165
- if (sttLoadError) throw sttLoadError;
167
+ if (sttLoadError && (Date.now() - sttLoadErrorTime < STT_RETRY_MS)) throw sttLoadError;
166
168
  if (!sttPipeline) throw new Error('STT pipeline failed to load');
167
169
  return sttPipeline;
168
170
  }
@@ -183,6 +185,7 @@ async function getSTT() {
183
185
  } catch (err) {
184
186
  sttPipeline = null;
185
187
  sttLoadError = new Error('STT model load failed: ' + err.message);
188
+ sttLoadErrorTime = Date.now();
186
189
  throw sttLoadError;
187
190
  } finally {
188
191
  sttLoading = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.181",
3
+ "version": "1.0.182",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1311,34 +1311,121 @@ function recoverStaleSessions() {
1311
1311
  try {
1312
1312
  const now = Date.now();
1313
1313
 
1314
+ const resumable = new Set();
1315
+ const resumableConvs = queries.getResumableConversations ? queries.getResumableConversations() : [];
1316
+ for (const conv of resumableConvs) {
1317
+ if (conv.agentType === 'claude-code') {
1318
+ resumable.add(conv.id);
1319
+ }
1320
+ }
1321
+
1314
1322
  const staleSessions = queries.getActiveSessions ? queries.getActiveSessions() : [];
1323
+ let markedCount = 0;
1315
1324
  for (const session of staleSessions) {
1316
1325
  if (activeExecutions.has(session.conversationId)) continue;
1326
+ if (resumable.has(session.conversationId)) continue;
1317
1327
  queries.updateSession(session.id, {
1318
1328
  status: 'error',
1319
1329
  error: 'Server restarted',
1320
1330
  completed_at: now
1321
1331
  });
1332
+ markedCount++;
1322
1333
  }
1323
- if (staleSessions.length > 0) {
1324
- console.log(`[RECOVERY] Marked ${staleSessions.length} stale session(s) as error`);
1334
+ if (markedCount > 0) {
1335
+ console.log(`[RECOVERY] Marked ${markedCount} stale session(s) as error`);
1325
1336
  }
1326
1337
 
1327
1338
  const streamingConvs = queries.getStreamingConversations ? queries.getStreamingConversations() : [];
1328
1339
  let clearedCount = 0;
1329
1340
  for (const conv of streamingConvs) {
1330
1341
  if (activeExecutions.has(conv.id)) continue;
1342
+ if (resumable.has(conv.id)) continue;
1331
1343
  queries.setIsStreaming(conv.id, false);
1332
1344
  clearedCount++;
1333
1345
  }
1334
1346
  if (clearedCount > 0) {
1335
1347
  console.log(`[RECOVERY] Cleared isStreaming flag on ${clearedCount} stale conversation(s)`);
1336
1348
  }
1349
+ if (resumable.size > 0) {
1350
+ console.log(`[RECOVERY] Found ${resumable.size} resumable conversation(s)`);
1351
+ }
1337
1352
  } catch (err) {
1338
1353
  console.error('[RECOVERY] Stale session recovery error:', err.message);
1339
1354
  }
1340
1355
  }
1341
1356
 
1357
+ async function resumeInterruptedStreams() {
1358
+ try {
1359
+ const resumableConvs = queries.getResumableConversations ? queries.getResumableConversations() : [];
1360
+ const toResume = resumableConvs.filter(c => c.agentType === 'claude-code');
1361
+
1362
+ if (toResume.length === 0) return;
1363
+
1364
+ console.log(`[RESUME] Resuming ${toResume.length} interrupted conversation(s)`);
1365
+
1366
+ for (let i = 0; i < toResume.length; i++) {
1367
+ const conv = toResume[i];
1368
+ try {
1369
+ const staleSessions = [...queries.getSessionsByStatus(conv.id, 'active'), ...queries.getSessionsByStatus(conv.id, 'pending')];
1370
+ for (const s of staleSessions) {
1371
+ queries.updateSession(s.id, { status: 'interrupted', error: 'Server restarted, resuming', completed_at: Date.now() });
1372
+ }
1373
+
1374
+ const lastMsg = queries.getLastUserMessage(conv.id);
1375
+ const prompt = lastMsg?.content || 'continue';
1376
+ const promptText = typeof prompt === 'string' ? prompt : JSON.stringify(prompt);
1377
+
1378
+ const session = queries.createSession(conv.id);
1379
+ queries.createEvent('session.created', {
1380
+ sessionId: session.id,
1381
+ resumeReason: 'server_restart',
1382
+ claudeSessionId: conv.claudeSessionId
1383
+ }, conv.id, session.id);
1384
+
1385
+ activeExecutions.set(conv.id, {
1386
+ pid: null,
1387
+ startTime: Date.now(),
1388
+ sessionId: session.id,
1389
+ lastActivity: Date.now()
1390
+ });
1391
+
1392
+ broadcastSync({
1393
+ type: 'streaming_start',
1394
+ sessionId: session.id,
1395
+ conversationId: conv.id,
1396
+ agentId: conv.agentType,
1397
+ resumed: true,
1398
+ timestamp: Date.now()
1399
+ });
1400
+
1401
+ const messageId = lastMsg?.id || null;
1402
+ console.log(`[RESUME] Resuming conv ${conv.id} (claude session: ${conv.claudeSessionId})`);
1403
+
1404
+ processMessageWithStreaming(conv.id, messageId, session.id, promptText, conv.agentType)
1405
+ .catch(err => debugLog(`[RESUME] Error resuming conv ${conv.id}: ${err.message}`));
1406
+
1407
+ if (i < toResume.length - 1) {
1408
+ await new Promise(r => setTimeout(r, 200));
1409
+ }
1410
+ } catch (err) {
1411
+ console.error(`[RESUME] Failed to resume conv ${conv.id}: ${err.message}`);
1412
+ queries.setIsStreaming(conv.id, false);
1413
+ const activeSessions = queries.getSessionsByStatus(conv.id, 'active');
1414
+ const pendingSessions = queries.getSessionsByStatus(conv.id, 'pending');
1415
+ for (const s of [...activeSessions, ...pendingSessions]) {
1416
+ queries.updateSession(s.id, {
1417
+ status: 'error',
1418
+ error: 'Resume failed: ' + err.message,
1419
+ completed_at: Date.now()
1420
+ });
1421
+ }
1422
+ }
1423
+ }
1424
+ } catch (err) {
1425
+ console.error('[RESUME] Error during stream resumption:', err.message);
1426
+ }
1427
+ }
1428
+
1342
1429
  function isProcessAlive(pid) {
1343
1430
  try {
1344
1431
  process.kill(pid, 0);
@@ -1414,6 +1501,9 @@ function onServerReady() {
1414
1501
  // Recover stale active sessions from previous run
1415
1502
  recoverStaleSessions();
1416
1503
 
1504
+ // Resume interrupted streams after recovery
1505
+ resumeInterruptedStreams().catch(err => console.error('[RESUME] Startup error:', err.message));
1506
+
1417
1507
  getSpeech().then(s => s.preloadTTS()).catch(e => debugLog('[TTS] Preload failed: ' + e.message));
1418
1508
 
1419
1509
  performAutoImport();