agentgui 1.0.148 → 1.0.149

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.
Files changed (3) hide show
  1. package/database.js +4 -4
  2. package/package.json +1 -1
  3. package/server.js +78 -39
package/database.js CHANGED
@@ -335,14 +335,14 @@ export const queries = {
335
335
 
336
336
  getSessionsProcessingLongerThan(minutes) {
337
337
  const cutoff = Date.now() - (minutes * 60 * 1000);
338
- const stmt = db.prepare('SELECT * FROM sessions WHERE status = ? AND started_at < ?');
339
- return stmt.all('pending', cutoff);
338
+ const stmt = db.prepare("SELECT * FROM sessions WHERE status IN ('active', 'pending') AND started_at < ?");
339
+ return stmt.all(cutoff);
340
340
  },
341
341
 
342
342
  cleanupOrphanedSessions(days) {
343
343
  const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
344
- const stmt = db.prepare('DELETE FROM sessions WHERE status = ? AND started_at < ?');
345
- const result = stmt.run('pending', cutoff);
344
+ const stmt = db.prepare("DELETE FROM sessions WHERE status IN ('active', 'pending') AND started_at < ?");
345
+ const result = stmt.run(cutoff);
346
346
  return result.changes || 0;
347
347
  },
348
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.148",
3
+ "version": "1.0.149",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -23,6 +23,9 @@ const SYSTEM_PROMPT = `Write all responses as clean semantic HTML. Use tags like
23
23
 
24
24
  const activeExecutions = new Map();
25
25
  const messageQueues = new Map();
26
+ const STUCK_AGENT_THRESHOLD_MS = 600000;
27
+ const NO_PID_GRACE_PERIOD_MS = 60000;
28
+ const STALE_SESSION_MIN_AGE_MS = 30000;
26
29
 
27
30
  const debugLog = (msg) => {
28
31
  const timestamp = new Date().toISOString();
@@ -648,7 +651,7 @@ function persistChunkWithRetry(sessionId, conversationId, sequence, blockType, b
648
651
 
649
652
  async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId) {
650
653
  const startTime = Date.now();
651
- activeExecutions.set(conversationId, { pid: null, startTime, sessionId });
654
+ activeExecutions.set(conversationId, { pid: null, startTime, sessionId, lastActivity: startTime });
652
655
  queries.setIsStreaming(conversationId, true);
653
656
  queries.updateSession(sessionId, { status: 'active' });
654
657
 
@@ -665,6 +668,8 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
665
668
 
666
669
  const onEvent = (parsed) => {
667
670
  eventCount++;
671
+ const entry = activeExecutions.get(conversationId);
672
+ if (entry) entry.lastActivity = Date.now();
668
673
  debugLog(`[stream] Event ${eventCount}: type=${parsed.type}`);
669
674
 
670
675
  if (parsed.type === 'system') {
@@ -1045,25 +1050,27 @@ server.on('error', (err) => {
1045
1050
  function recoverStaleSessions() {
1046
1051
  try {
1047
1052
  const staleSessions = queries.getActiveSessions ? queries.getActiveSessions() : [];
1053
+ const now = Date.now();
1048
1054
  let recoveredCount = 0;
1049
1055
  for (const session of staleSessions) {
1050
- if (!activeExecutions.has(session.conversationId)) {
1051
- queries.updateSession(session.id, {
1052
- status: 'error',
1053
- error: 'Agent died unexpectedly (server restart)',
1054
- completed_at: Date.now()
1055
- });
1056
- queries.setIsStreaming(session.conversationId, false);
1057
- broadcastSync({
1058
- type: 'streaming_error',
1059
- sessionId: session.id,
1060
- conversationId: session.conversationId,
1061
- error: 'Agent died unexpectedly (server restart)',
1062
- recoverable: false,
1063
- timestamp: Date.now()
1064
- });
1065
- recoveredCount++;
1066
- }
1056
+ if (activeExecutions.has(session.conversationId)) continue;
1057
+ const sessionAge = now - session.started_at;
1058
+ if (sessionAge < STALE_SESSION_MIN_AGE_MS) continue;
1059
+ queries.updateSession(session.id, {
1060
+ status: 'error',
1061
+ error: 'Agent died unexpectedly (server restart)',
1062
+ completed_at: now
1063
+ });
1064
+ queries.setIsStreaming(session.conversationId, false);
1065
+ broadcastSync({
1066
+ type: 'streaming_error',
1067
+ sessionId: session.id,
1068
+ conversationId: session.conversationId,
1069
+ error: 'Agent died unexpectedly (server restart)',
1070
+ recoverable: false,
1071
+ timestamp: now
1072
+ });
1073
+ recoveredCount++;
1067
1074
  }
1068
1075
  if (recoveredCount > 0) {
1069
1076
  console.log(`[RECOVERY] Recovered ${recoveredCount} stale active session(s)`);
@@ -1073,31 +1080,63 @@ function recoverStaleSessions() {
1073
1080
  }
1074
1081
  }
1075
1082
 
1083
+ function isProcessAlive(pid) {
1084
+ try {
1085
+ process.kill(pid, 0);
1086
+ return true;
1087
+ } catch (err) {
1088
+ if (err.code === 'EPERM') return true;
1089
+ return false;
1090
+ }
1091
+ }
1092
+
1093
+ function markAgentDead(conversationId, entry, reason) {
1094
+ if (!activeExecutions.has(conversationId)) return;
1095
+ activeExecutions.delete(conversationId);
1096
+ queries.setIsStreaming(conversationId, false);
1097
+ if (entry.sessionId) {
1098
+ queries.updateSession(entry.sessionId, {
1099
+ status: 'error',
1100
+ error: reason,
1101
+ completed_at: Date.now()
1102
+ });
1103
+ }
1104
+ broadcastSync({
1105
+ type: 'streaming_error',
1106
+ sessionId: entry.sessionId,
1107
+ conversationId,
1108
+ error: reason,
1109
+ recoverable: false,
1110
+ timestamp: Date.now()
1111
+ });
1112
+ drainMessageQueue(conversationId);
1113
+ }
1114
+
1076
1115
  function performAgentHealthCheck() {
1116
+ const now = Date.now();
1077
1117
  for (const [conversationId, entry] of activeExecutions) {
1078
- if (!entry || !entry.pid) continue;
1079
- try {
1080
- process.kill(entry.pid, 0);
1081
- } catch (err) {
1082
- debugLog(`[HEALTH] Agent PID ${entry.pid} for conv ${conversationId} is dead`);
1083
- activeExecutions.delete(conversationId);
1084
- queries.setIsStreaming(conversationId, false);
1085
- if (entry.sessionId) {
1086
- queries.updateSession(entry.sessionId, {
1087
- status: 'error',
1088
- error: 'Agent process died unexpectedly',
1089
- completed_at: Date.now()
1118
+ if (!entry) continue;
1119
+
1120
+ if (entry.pid) {
1121
+ if (!isProcessAlive(entry.pid)) {
1122
+ debugLog(`[HEALTH] Agent PID ${entry.pid} for conv ${conversationId} is dead`);
1123
+ markAgentDead(conversationId, entry, 'Agent process died unexpectedly');
1124
+ } else if (now - entry.lastActivity > STUCK_AGENT_THRESHOLD_MS) {
1125
+ debugLog(`[HEALTH] Agent PID ${entry.pid} for conv ${conversationId} has no activity for ${Math.round((now - entry.lastActivity) / 1000)}s`);
1126
+ broadcastSync({
1127
+ type: 'streaming_error',
1128
+ sessionId: entry.sessionId,
1129
+ conversationId,
1130
+ error: 'Agent may be stuck (no activity for 10 minutes)',
1131
+ recoverable: true,
1132
+ timestamp: now
1090
1133
  });
1091
1134
  }
1092
- broadcastSync({
1093
- type: 'streaming_error',
1094
- sessionId: entry.sessionId,
1095
- conversationId,
1096
- error: 'Agent process died unexpectedly',
1097
- recoverable: false,
1098
- timestamp: Date.now()
1099
- });
1100
- drainMessageQueue(conversationId);
1135
+ } else {
1136
+ if (now - entry.startTime > NO_PID_GRACE_PERIOD_MS) {
1137
+ debugLog(`[HEALTH] Agent for conv ${conversationId} never reported PID after ${Math.round((now - entry.startTime) / 1000)}s`);
1138
+ markAgentDead(conversationId, entry, 'Agent failed to start (no PID reported)');
1139
+ }
1101
1140
  }
1102
1141
  }
1103
1142
  }