@link-assistant/hive-mind 1.21.3 → 1.22.0

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.
@@ -43,13 +43,14 @@ export const QueueItemStatus = {
43
43
  };
44
44
 
45
45
  /**
46
- * Count running claude processes
46
+ * Count running processes by name
47
+ * @param {string} processName - Process name to search for (e.g., 'claude', 'agent')
47
48
  * @param {boolean} verbose - Whether to log verbose output
48
49
  * @returns {Promise<{count: number, processes: string[]}>}
49
50
  */
50
- export async function getRunningClaudeProcesses(verbose = false) {
51
+ export async function getRunningProcesses(processName, verbose = false) {
51
52
  try {
52
- const { stdout } = await execAsync('pgrep -l -x claude 2>/dev/null || true');
53
+ const { stdout } = await execAsync(`pgrep -l -x ${processName} 2>/dev/null || true`);
53
54
  const lines = stdout
54
55
  .trim()
55
56
  .split('\n')
@@ -60,13 +61,13 @@ export async function getRunningClaudeProcesses(verbose = false) {
60
61
  const parts = line.trim().split(/\s+/);
61
62
  return {
62
63
  pid: parts[0],
63
- name: parts.slice(1).join(' ') || 'claude',
64
+ name: parts.slice(1).join(' ') || processName,
64
65
  };
65
66
  })
66
67
  .filter(p => p.pid);
67
68
 
68
69
  if (verbose) {
69
- console.log(`[VERBOSE] /solve_queue found ${processes.length} running claude processes`);
70
+ console.log(`[VERBOSE] /solve_queue found ${processes.length} running ${processName} processes`);
70
71
  if (processes.length > 0) {
71
72
  console.log(`[VERBOSE] /solve_queue processes: ${JSON.stringify(processes)}`);
72
73
  }
@@ -78,12 +79,30 @@ export async function getRunningClaudeProcesses(verbose = false) {
78
79
  };
79
80
  } catch (error) {
80
81
  if (verbose) {
81
- console.error('[VERBOSE] /solve_queue error counting claude processes:', error.message);
82
+ console.error(`[VERBOSE] /solve_queue error counting ${processName} processes:`, error.message);
82
83
  }
83
84
  return { count: 0, processes: [] };
84
85
  }
85
86
  }
86
87
 
88
+ /**
89
+ * Count running claude processes
90
+ * @param {boolean} verbose - Whether to log verbose output
91
+ * @returns {Promise<{count: number, processes: string[]}>}
92
+ */
93
+ export async function getRunningClaudeProcesses(verbose = false) {
94
+ return getRunningProcesses('claude', verbose);
95
+ }
96
+
97
+ /**
98
+ * Count running agent processes
99
+ * @param {boolean} verbose - Whether to log verbose output
100
+ * @returns {Promise<{count: number, processes: string[]}>}
101
+ */
102
+ export async function getRunningAgentProcesses(verbose = false) {
103
+ return getRunningProcesses('agent', verbose);
104
+ }
105
+
87
106
  /**
88
107
  * Format a threshold as percentage for display
89
108
  * @param {number} ratio - Ratio (0.0 - 1.0)
@@ -93,6 +112,33 @@ function formatThresholdPercent(ratio) {
93
112
  return `${Math.round(ratio * 100)}%`;
94
113
  }
95
114
 
115
+ /**
116
+ * Format milliseconds into human-readable duration
117
+ * Shows days, hours, minutes, and seconds as appropriate.
118
+ * Examples: "5h 43m 23s", "2m 15s", "45s", "1d 3h 12m 5s"
119
+ *
120
+ * @param {number} ms - Duration in milliseconds
121
+ * @returns {string} Human-readable duration
122
+ * @see https://github.com/link-assistant/hive-mind/issues/1267
123
+ */
124
+ export function formatDuration(ms) {
125
+ if (ms < 0) ms = 0;
126
+
127
+ const totalSeconds = Math.floor(ms / 1000);
128
+ const days = Math.floor(totalSeconds / 86400);
129
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
130
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
131
+ const seconds = totalSeconds % 60;
132
+
133
+ const parts = [];
134
+ if (days > 0) parts.push(`${days}d`);
135
+ if (hours > 0) parts.push(`${hours}h`);
136
+ if (minutes > 0) parts.push(`${minutes}m`);
137
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
138
+
139
+ return parts.join(' ');
140
+ }
141
+
96
142
  /**
97
143
  * Generate human-readable waiting reason based on threshold violation
98
144
  * @param {string} metric - The metric name (ram, cpu, disk, etc.)
@@ -1035,7 +1081,11 @@ export class SolveQueue {
1035
1081
  const itemCheck = await this.canStartCommand({ tool: item.tool });
1036
1082
  const previousStatus = item.status;
1037
1083
  const previousReason = item.waitingReason;
1038
- item.setWaiting(itemCheck.reason || 'Waiting in queue');
1084
+ // Use rejectReason when threshold strategy is 'reject', otherwise use reason
1085
+ // This ensures disk-full and other rejection reasons are shown properly
1086
+ // See: https://github.com/link-assistant/hive-mind/issues/1267
1087
+ const waitReason = itemCheck.rejectReason || itemCheck.reason || 'Waiting in queue';
1088
+ item.setWaiting(waitReason);
1039
1089
 
1040
1090
  // Update message if status/reason changed or it's time for periodic update
1041
1091
  const shouldUpdate = previousStatus !== item.status || previousReason !== item.waitingReason || this.shouldUpdateMessage(item);
@@ -1158,74 +1208,113 @@ export class SolveQueue {
1158
1208
  }
1159
1209
 
1160
1210
  /**
1161
- * Format queue status for display
1162
- * Shows per-tool queue counts.
1163
- * @returns {string}
1211
+ * Format queue status for display in /limits command
1212
+ * Shows per-tool queue breakdown with processing counts.
1213
+ *
1214
+ * Processing count = actual running system processes (via pgrep), not items in queue processing state.
1215
+ * This is because items transition quickly through the processing state, but the actual
1216
+ * work happens in the spawned system process (claude, agent, etc.).
1217
+ *
1218
+ * Output format:
1219
+ * ```
1220
+ * Queues
1221
+ * claude (pending: 6, processing: 0)
1222
+ * agent (pending: 2, processing: 0)
1223
+ * ```
1224
+ *
1225
+ * @returns {Promise<string>}
1164
1226
  * @see https://github.com/link-assistant/hive-mind/issues/1159
1227
+ * @see https://github.com/link-assistant/hive-mind/issues/1267
1165
1228
  */
1166
- formatStatus() {
1167
- const stats = this.getStats();
1168
- if (stats.queued > 0 || stats.processing > 0) {
1169
- // Show per-tool breakdown if there are items
1170
- const toolBreakdown = Object.entries(stats.queuedByTool)
1171
- .filter(entry => entry[1] > 0)
1172
- .map(([tool, count]) => `${tool}: ${count}`)
1173
- .join(', ');
1174
- const queueInfo = toolBreakdown ? ` (${toolBreakdown})` : '';
1175
- return `Solve Queue: ${stats.queued} pending${queueInfo}, ${stats.processing} processing\n`;
1229
+ async formatStatus() {
1230
+ // Get actual process counts for each tool queue
1231
+ // The "processing" count is the number of running system processes, not queue internal state
1232
+ // This ensures users see accurate counts of what's actually running
1233
+ const claudeProcs = await getRunningClaudeProcesses(this.verbose);
1234
+ const agentProcs = await getRunningAgentProcesses(this.verbose);
1235
+
1236
+ const processCounts = {
1237
+ claude: claudeProcs.count,
1238
+ agent: agentProcs.count,
1239
+ };
1240
+
1241
+ // Always show per-tool breakdown for all known queues
1242
+ let message = 'Queues\n';
1243
+ for (const [tool, toolQueue] of Object.entries(this.queues)) {
1244
+ const pending = toolQueue.length;
1245
+ const processing = processCounts[tool] || 0;
1246
+ message += `${tool} (pending: ${pending}, processing: ${processing})\n`;
1176
1247
  }
1177
- return 'Solve Queue: empty\n';
1248
+
1249
+ return message;
1178
1250
  }
1179
1251
 
1180
1252
  /**
1181
1253
  * Format detailed queue status for Telegram message
1182
- * Shows per-tool queue breakdown.
1183
- * @returns {string}
1254
+ * Groups output by tool queue, shows first 5 items per queue, and uses human-readable time.
1255
+ *
1256
+ * Processing count = actual running system processes (via pgrep), not items in queue processing state.
1257
+ * This is because items transition quickly through the processing state, but the actual
1258
+ * work happens in the spawned system process (claude, agent, etc.).
1259
+ *
1260
+ * Output format:
1261
+ * ```
1262
+ * 📋 Solve Queue Status
1263
+ *
1264
+ * claude (pending: 6, processing: 0)
1265
+ * • url1 (waiting, 5h 43m 23s)
1266
+ * └ RAM usage is 70% (threshold: 65%)
1267
+ * • url2 (queued, 2m 15s)
1268
+ *
1269
+ * agent (pending: 2, processing: 0)
1270
+ * • url3 (waiting, 1h 2m 5s)
1271
+ * ```
1272
+ *
1273
+ * @returns {Promise<string>}
1184
1274
  * @see https://github.com/link-assistant/hive-mind/issues/1159
1275
+ * @see https://github.com/link-assistant/hive-mind/issues/1267
1185
1276
  */
1186
- formatDetailedStatus() {
1277
+ async formatDetailedStatus() {
1187
1278
  const stats = this.getStats();
1188
- const summary = this.getQueueSummary();
1189
1279
 
1190
- let message = '📋 *Solve Queue Status*\n\n';
1191
- message += `Pending: ${stats.queued}`;
1192
-
1193
- // Add per-tool breakdown
1194
- const toolBreakdown = Object.entries(stats.queuedByTool)
1195
- .filter(entry => entry[1] > 0)
1196
- .map(([tool, count]) => `${tool}: ${count}`)
1197
- .join(', ');
1198
- if (toolBreakdown) {
1199
- message += ` (${toolBreakdown})`;
1200
- }
1201
- message += '\n';
1280
+ // Get actual process counts for each tool queue
1281
+ // The "processing" count is the number of running system processes, not queue internal state
1282
+ // This ensures users see accurate counts of what's actually running
1283
+ const claudeProcs = await getRunningClaudeProcesses(this.verbose);
1284
+ const agentProcs = await getRunningAgentProcesses(this.verbose);
1202
1285
 
1203
- message += `Processing: ${stats.processing}\n`;
1204
- message += `Completed: ${stats.completed}\n`;
1205
- message += `Failed: ${stats.failed}\n\n`;
1286
+ const processCounts = {
1287
+ claude: claudeProcs.count,
1288
+ agent: agentProcs.count,
1289
+ };
1206
1290
 
1207
- if (summary.processing.length > 0) {
1208
- message += '*Currently Processing:*\n';
1209
- for (const item of summary.processing) {
1210
- message += `• ${item.url} [${item.tool}]\n`;
1211
- }
1212
- message += '\n';
1213
- }
1291
+ let message = '📋 *Solve Queue Status*\n\n';
1214
1292
 
1215
- if (summary.pending.length > 0) {
1216
- message += '*Waiting in Queue:*\n';
1217
- for (const item of summary.pending.slice(0, 5)) {
1218
- const waitSeconds = Math.floor(item.waitTime / 1000);
1219
- message += `• ${item.url} [${item.tool}] (${item.status}, ${waitSeconds}s)\n`;
1293
+ // Show per-tool queue breakdown with items grouped by queue
1294
+ for (const [tool, toolQueue] of Object.entries(this.queues)) {
1295
+ const pending = toolQueue.length;
1296
+ const processing = processCounts[tool] || 0;
1297
+ message += `*${tool}* (pending: ${pending}, processing: ${processing})\n`;
1298
+
1299
+ // Show first 5 queued items for this tool
1300
+ const displayItems = toolQueue.slice(0, 5);
1301
+ for (const item of displayItems) {
1302
+ const waitTime = formatDuration(item.getWaitTime());
1303
+ message += ` • ${item.url} (${item.status}, ${waitTime})\n`;
1220
1304
  if (item.waitingReason) {
1221
- message += ` └ ${item.waitingReason}\n`;
1305
+ message += ` └ ${item.waitingReason}\n`;
1222
1306
  }
1223
1307
  }
1224
- if (summary.pending.length > 5) {
1225
- message += ` ... and ${summary.pending.length - 5} more\n`;
1308
+ if (toolQueue.length > 5) {
1309
+ message += ` ... and ${toolQueue.length - 5} more\n`;
1226
1310
  }
1311
+
1312
+ message += '\n';
1227
1313
  }
1228
1314
 
1315
+ // Summary stats
1316
+ message += `Completed: ${stats.completed}, Failed: ${stats.failed}\n`;
1317
+
1229
1318
  return message;
1230
1319
  }
1231
1320
  }
@@ -1275,8 +1364,11 @@ export default {
1275
1364
  SolveQueueItem,
1276
1365
  getSolveQueue,
1277
1366
  resetSolveQueue,
1367
+ getRunningProcesses,
1278
1368
  getRunningClaudeProcesses,
1369
+ getRunningAgentProcesses,
1279
1370
  createQueueExecuteCallback,
1371
+ formatDuration,
1280
1372
  QUEUE_CONFIG,
1281
1373
  QueueItemStatus,
1282
1374
  };