@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.
- package/CHANGELOG.md +31 -0
- package/package.json +2 -2
- package/src/agent.lib.mjs +59 -0
- package/src/claude.lib.mjs +21 -20
- package/src/codex.lib.mjs +34 -0
- package/src/github-merge.lib.mjs +27 -3
- package/src/limits.lib.mjs +18 -16
- package/src/opencode.lib.mjs +78 -0
- package/src/solve.config.lib.mjs +10 -0
- package/src/solve.mjs +37 -1
- package/src/solve.results.lib.mjs +130 -0
- package/src/telegram-bot.mjs +17 -10
- package/src/telegram-merge-command.lib.mjs +59 -8
- package/src/telegram-merge-queue.lib.mjs +18 -0
- package/src/telegram-solve-queue-command.lib.mjs +5 -7
- package/src/telegram-solve-queue.lib.mjs +147 -55
|
@@ -43,13 +43,14 @@ export const QueueItemStatus = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Count running
|
|
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
|
|
51
|
+
export async function getRunningProcesses(processName, verbose = false) {
|
|
51
52
|
try {
|
|
52
|
-
const { stdout } = await execAsync(
|
|
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(' ') ||
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
|
|
1248
|
+
|
|
1249
|
+
return message;
|
|
1178
1250
|
}
|
|
1179
1251
|
|
|
1180
1252
|
/**
|
|
1181
1253
|
* Format detailed queue status for Telegram message
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
const
|
|
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
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1286
|
+
const processCounts = {
|
|
1287
|
+
claude: claudeProcs.count,
|
|
1288
|
+
agent: agentProcs.count,
|
|
1289
|
+
};
|
|
1206
1290
|
|
|
1207
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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 += `
|
|
1305
|
+
message += ` └ ${item.waitingReason}\n`;
|
|
1222
1306
|
}
|
|
1223
1307
|
}
|
|
1224
|
-
if (
|
|
1225
|
-
message += `
|
|
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
|
};
|