@link-assistant/hive-mind 2.11.13 → 2.12.1
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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @see https://github.com/link-assistant/hive-mind/issues/1041
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
12
|
import { getCachedClaudeLimits, getCachedCodexLimits, getCachedGitHubLimits, getCachedMemoryInfo, getCachedCpuInfo, getCachedDiskInfo, getLimitCache } from './limits.lib.mjs';
|
|
14
13
|
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
15
14
|
import { collectExecutingItems, formatDuration, formatQueueToolSection, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses, getRunningSessionItems, groupQueueItemsByTool, reportDequeueDecision } from './telegram-solve-queue.helpers.lib.mjs';
|
|
@@ -19,7 +18,6 @@ import { reserveStartSlotForQueue } from './queue-start-reservation.lib.mjs';
|
|
|
19
18
|
import { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
20
19
|
import { t } from './i18n.lib.mjs';
|
|
21
20
|
import { lt } from './limits-i18n.lib.mjs';
|
|
22
|
-
|
|
23
21
|
export const QueueItemStatus = {
|
|
24
22
|
QUEUED: 'queued',
|
|
25
23
|
WAITING: 'waiting',
|
|
@@ -28,20 +26,16 @@ export const QueueItemStatus = {
|
|
|
28
26
|
FAILED: 'failed',
|
|
29
27
|
CANCELLED: 'cancelled',
|
|
30
28
|
};
|
|
31
|
-
|
|
32
29
|
function getLocale(options = {}) {
|
|
33
30
|
if (typeof options === 'string') return options;
|
|
34
31
|
return options?.locale || null;
|
|
35
32
|
}
|
|
36
|
-
|
|
37
33
|
function appendWaitingForCurrentCommand(reason, locale) {
|
|
38
34
|
return `${reason} (${lt('queue_waiting_current_command', {}, { locale })})`;
|
|
39
35
|
}
|
|
40
|
-
|
|
41
36
|
function appendRemainingDuration(reason, ms, locale) {
|
|
42
37
|
return `${reason} (${lt('remaining', { duration: formatDuration(ms, { locale }) }, { locale })})`;
|
|
43
38
|
}
|
|
44
|
-
|
|
45
39
|
/**
|
|
46
40
|
* Queue item representing a /solve command request
|
|
47
41
|
*/
|
|
@@ -57,8 +51,7 @@ class SolveQueueItem {
|
|
|
57
51
|
this.tool = options.tool || 'claude';
|
|
58
52
|
// Issue #1983: preserve per-command isolation through queued execution.
|
|
59
53
|
this.perCommandIsolation = options.perCommandIsolation || null;
|
|
60
|
-
// Issue #1688: keep parsed URL context (owner/repo/number/type) so completion
|
|
61
|
-
// notifications can look up linked PRs for issue URLs.
|
|
54
|
+
// Issue #1688: keep parsed URL context (owner/repo/number/type) so completion notifications can look up linked PRs for issue URLs.
|
|
62
55
|
this.urlContext = options.urlContext || null;
|
|
63
56
|
// Issue #1688: requester user ID for /subscribe duplicate-suppression.
|
|
64
57
|
this.requesterUserId = options.ctx?.from?.id ?? null;
|
|
@@ -75,11 +68,9 @@ class SolveQueueItem {
|
|
|
75
68
|
this.sessionName = null;
|
|
76
69
|
// Message tracking - forget after STARTED
|
|
77
70
|
this.messageInfo = null; // { chatId, messageId }
|
|
78
|
-
// Track when we last updated the Telegram message
|
|
79
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
71
|
+
// Track when we last updated the Telegram message See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
80
72
|
this.lastMessageUpdateTime = null;
|
|
81
73
|
}
|
|
82
|
-
|
|
83
74
|
/**
|
|
84
75
|
* Update status to waiting with reason
|
|
85
76
|
* @param {string} reason - Waiting reason
|
|
@@ -88,7 +79,6 @@ class SolveQueueItem {
|
|
|
88
79
|
this.status = QueueItemStatus.WAITING;
|
|
89
80
|
this.waitingReason = reason;
|
|
90
81
|
}
|
|
91
|
-
|
|
92
82
|
/**
|
|
93
83
|
* Update status to starting
|
|
94
84
|
*/
|
|
@@ -97,7 +87,6 @@ class SolveQueueItem {
|
|
|
97
87
|
this.startedAt = new Date();
|
|
98
88
|
this.waitingReason = null;
|
|
99
89
|
}
|
|
100
|
-
|
|
101
90
|
/**
|
|
102
91
|
* Update status to started and clear message tracking
|
|
103
92
|
* @param {string} sessionName - Session name for debugging
|
|
@@ -108,7 +97,6 @@ class SolveQueueItem {
|
|
|
108
97
|
// Terminal status - forget message tracking
|
|
109
98
|
this.messageInfo = null;
|
|
110
99
|
}
|
|
111
|
-
|
|
112
100
|
/**
|
|
113
101
|
* Mark item as failed
|
|
114
102
|
* @param {Error|string} error - Error that occurred
|
|
@@ -117,14 +105,12 @@ class SolveQueueItem {
|
|
|
117
105
|
this.status = QueueItemStatus.FAILED;
|
|
118
106
|
this.error = error instanceof Error ? error.message : error;
|
|
119
107
|
}
|
|
120
|
-
|
|
121
108
|
/**
|
|
122
109
|
* Mark item as cancelled
|
|
123
110
|
*/
|
|
124
111
|
setCancelled() {
|
|
125
112
|
this.status = QueueItemStatus.CANCELLED;
|
|
126
113
|
}
|
|
127
|
-
|
|
128
114
|
/**
|
|
129
115
|
* Get wait time in queue (ms)
|
|
130
116
|
*/
|
|
@@ -132,7 +118,6 @@ class SolveQueueItem {
|
|
|
132
118
|
const endTime = this.startedAt || new Date();
|
|
133
119
|
return endTime - this.createdAt;
|
|
134
120
|
}
|
|
135
|
-
|
|
136
121
|
/**
|
|
137
122
|
* Format for display
|
|
138
123
|
* @returns {string}
|
|
@@ -141,7 +126,6 @@ class SolveQueueItem {
|
|
|
141
126
|
return `[${this.id}] ${this.url} (${this.status})`;
|
|
142
127
|
}
|
|
143
128
|
}
|
|
144
|
-
|
|
145
129
|
/**
|
|
146
130
|
* Solve Queue - Producer/Consumer queue for /solve commands
|
|
147
131
|
*
|
|
@@ -159,13 +143,10 @@ export class SolveQueue {
|
|
|
159
143
|
this.messageUpdateCallback = options.messageUpdateCallback || null;
|
|
160
144
|
this.getRunningProcessesFn = options.getRunningProcesses || getRunningProcesses;
|
|
161
145
|
this.getRunningIsolatedSessionsFn = options.getRunningIsolatedSessions || getRunningIsolatedSessions;
|
|
162
|
-
// Source of currently-executing detached sessions (with issue/PR URLs) used
|
|
163
|
-
// to list executing tasks in the detailed status (issue #1837).
|
|
146
|
+
// Source of currently-executing detached sessions (with issue/PR URLs) used to list executing tasks in the detailed status (issue #1837).
|
|
164
147
|
this.getRunningSessionItemsFn = options.getRunningSessionItems || getRunningSessionItems;
|
|
165
148
|
this.autoStart = options.autoStart !== false;
|
|
166
|
-
|
|
167
|
-
// Separate queues per tool type - claude tasks never block other tool tasks
|
|
168
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
149
|
+
// Separate queues per tool type - claude tasks never block other tool tasks See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
169
150
|
this.queues = {
|
|
170
151
|
claude: [],
|
|
171
152
|
agent: [],
|
|
@@ -177,7 +158,6 @@ export class SolveQueue {
|
|
|
177
158
|
this.completed = [];
|
|
178
159
|
this.failed = [];
|
|
179
160
|
this.isRunning = true;
|
|
180
|
-
|
|
181
161
|
// Timing - separate per tool to ensure independent processing
|
|
182
162
|
this.lastStartTimeByTool = {
|
|
183
163
|
claude: null,
|
|
@@ -187,10 +167,8 @@ export class SolveQueue {
|
|
|
187
167
|
gemini: null,
|
|
188
168
|
};
|
|
189
169
|
this.lastStartTime = null; // Legacy: global last-start timestamp
|
|
190
|
-
|
|
191
170
|
// Consumer task reference
|
|
192
171
|
this.consumerTask = null;
|
|
193
|
-
|
|
194
172
|
// Statistics
|
|
195
173
|
this.stats = {
|
|
196
174
|
totalEnqueued: 0,
|
|
@@ -200,10 +178,8 @@ export class SolveQueue {
|
|
|
200
178
|
totalCancelled: 0,
|
|
201
179
|
throttleReasons: {},
|
|
202
180
|
};
|
|
203
|
-
|
|
204
181
|
this.log('SolveQueue initialized with separate tool queues');
|
|
205
182
|
}
|
|
206
|
-
|
|
207
183
|
/**
|
|
208
184
|
* Get the queue array for a specific tool, creating it if needed
|
|
209
185
|
* @param {string} tool - Tool type ('claude', 'agent', 'codex', 'gemini', etc.)
|
|
@@ -215,7 +191,6 @@ export class SolveQueue {
|
|
|
215
191
|
}
|
|
216
192
|
return this.queues[tool];
|
|
217
193
|
}
|
|
218
|
-
|
|
219
194
|
/**
|
|
220
195
|
* Get combined queue length across all tools (for backwards compatibility)
|
|
221
196
|
* @returns {number} Total queue length
|
|
@@ -227,7 +202,6 @@ export class SolveQueue {
|
|
|
227
202
|
}
|
|
228
203
|
return total;
|
|
229
204
|
}
|
|
230
|
-
|
|
231
205
|
/**
|
|
232
206
|
* Get total pending count across all tool queues
|
|
233
207
|
* @returns {number} Total pending items
|
|
@@ -239,7 +213,6 @@ export class SolveQueue {
|
|
|
239
213
|
}
|
|
240
214
|
return total;
|
|
241
215
|
}
|
|
242
|
-
|
|
243
216
|
/**
|
|
244
217
|
* Log message if verbose mode is enabled
|
|
245
218
|
* @param {string} message
|
|
@@ -249,7 +222,6 @@ export class SolveQueue {
|
|
|
249
222
|
console.log(`[VERBOSE] /queue: ${message}`);
|
|
250
223
|
}
|
|
251
224
|
}
|
|
252
|
-
|
|
253
225
|
/**
|
|
254
226
|
* Add a solve command to the appropriate tool queue
|
|
255
227
|
* Items are added to the queue for their specific tool type.
|
|
@@ -262,15 +234,11 @@ export class SolveQueue {
|
|
|
262
234
|
const toolQueue = this.getToolQueue(item.tool);
|
|
263
235
|
toolQueue.push(item);
|
|
264
236
|
this.stats.totalEnqueued++;
|
|
265
|
-
|
|
266
237
|
this.log(`Enqueued: ${item.toString()} to ${item.tool} queue, queue length: ${toolQueue.length}`);
|
|
267
|
-
|
|
268
238
|
// Start consumer if not already running
|
|
269
239
|
if (this.autoStart) this.ensureConsumerRunning();
|
|
270
|
-
|
|
271
240
|
return item;
|
|
272
241
|
}
|
|
273
|
-
|
|
274
242
|
/**
|
|
275
243
|
* Find an item by URL in any queue or processing items
|
|
276
244
|
* Used to prevent duplicate URLs from being added to the queue
|
|
@@ -286,17 +254,14 @@ export class SolveQueue {
|
|
|
286
254
|
return queuedItem;
|
|
287
255
|
}
|
|
288
256
|
}
|
|
289
|
-
|
|
290
257
|
// Check processing items
|
|
291
258
|
for (const item of this.processing.values()) {
|
|
292
259
|
if (item.url === url) {
|
|
293
260
|
return item;
|
|
294
261
|
}
|
|
295
262
|
}
|
|
296
|
-
|
|
297
263
|
return null;
|
|
298
264
|
}
|
|
299
|
-
|
|
300
265
|
/**
|
|
301
266
|
* Cancel a queued item by ID
|
|
302
267
|
* Searches all tool queues to find the item.
|
|
@@ -316,15 +281,12 @@ export class SolveQueue {
|
|
|
316
281
|
return true;
|
|
317
282
|
}
|
|
318
283
|
}
|
|
319
|
-
|
|
320
284
|
if (this.processing.has(id)) {
|
|
321
285
|
this.log(`Cannot cancel processing item: ${id}`);
|
|
322
286
|
return false;
|
|
323
287
|
}
|
|
324
|
-
|
|
325
288
|
return false;
|
|
326
289
|
}
|
|
327
|
-
|
|
328
290
|
/**
|
|
329
291
|
* Get queue statistics
|
|
330
292
|
* @returns {Object}
|
|
@@ -337,7 +299,6 @@ export class SolveQueue {
|
|
|
337
299
|
queuedByTool[tool] = toolQueue.length;
|
|
338
300
|
totalQueued += toolQueue.length;
|
|
339
301
|
}
|
|
340
|
-
|
|
341
302
|
return {
|
|
342
303
|
queued: totalQueued,
|
|
343
304
|
queuedByTool,
|
|
@@ -351,7 +312,6 @@ export class SolveQueue {
|
|
|
351
312
|
isRunning: this.isRunning,
|
|
352
313
|
};
|
|
353
314
|
}
|
|
354
|
-
|
|
355
315
|
/**
|
|
356
316
|
* Count processing items by tool type
|
|
357
317
|
* Used for tool-specific limit checking - e.g., Claude limits only count Claude processing items
|
|
@@ -368,17 +328,14 @@ export class SolveQueue {
|
|
|
368
328
|
}
|
|
369
329
|
return count;
|
|
370
330
|
}
|
|
371
|
-
|
|
372
331
|
recordStart(tool = 'claude', startTime = Date.now()) {
|
|
373
332
|
this.lastStartTimeByTool[tool] = startTime;
|
|
374
333
|
this.lastStartTime = startTime;
|
|
375
334
|
return startTime;
|
|
376
335
|
}
|
|
377
|
-
|
|
378
336
|
reserveStartSlot(options = {}) {
|
|
379
337
|
return reserveStartSlotForQueue(this, options);
|
|
380
338
|
}
|
|
381
|
-
|
|
382
339
|
/**
|
|
383
340
|
* Find the next startable item across all tool queues. Each tool is checked
|
|
384
341
|
* independently so tool-specific limits do not block unrelated tools; issue
|
|
@@ -393,56 +350,41 @@ export class SolveQueue {
|
|
|
393
350
|
*/
|
|
394
351
|
async findStartableItems() {
|
|
395
352
|
const startableItems = [];
|
|
396
|
-
// Per-tool head diagnostics: why each queue head is/isn't startable, so
|
|
397
|
-
// global FIFO ordering can be audited in production (issue #2051).
|
|
353
|
+
// Per-tool head diagnostics: why each queue head is/isn't startable, so global FIFO ordering can be audited in production (issue #2051).
|
|
398
354
|
const headDiagnostics = [];
|
|
399
|
-
|
|
400
355
|
for (const [tool, toolQueue] of Object.entries(this.queues)) {
|
|
401
356
|
if (toolQueue.length === 0) continue;
|
|
402
|
-
|
|
403
357
|
// Check if first item in this tool's queue can start
|
|
404
358
|
const check = await this.canStartCommand({ tool, locale: toolQueue[0]?.locale || null });
|
|
405
|
-
|
|
406
|
-
// When a 'reject' strategy threshold is exceeded, immediately reject
|
|
407
|
-
// all items in this tool's queue instead of leaving them waiting.
|
|
408
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1555
|
|
359
|
+
// When a 'reject' strategy threshold is exceeded, immediately reject all items in this tool's queue instead of leaving them waiting. See: https://github.com/link-assistant/hive-mind/issues/1555
|
|
409
360
|
if (check.rejected) {
|
|
410
361
|
await this.rejectAllItemsInQueue(tool, toolQueue, check.rejectReason);
|
|
411
362
|
continue;
|
|
412
363
|
}
|
|
413
|
-
|
|
414
364
|
const item = toolQueue[0];
|
|
415
365
|
if (!item) continue;
|
|
416
|
-
|
|
417
|
-
// Determine startability and capture the blocking reason(s) for diagnostics.
|
|
418
|
-
// For tool-specific one-at-a-time, only count that tool's processing items.
|
|
366
|
+
// Determine startability and capture the blocking reason(s) for diagnostics. For tool-specific one-at-a-time, only count that tool's processing items.
|
|
419
367
|
const toolProcessingCount = this.getProcessingCountByTool(tool);
|
|
420
368
|
let startable = false;
|
|
421
369
|
const blockReasons = Array.isArray(check.reasons) ? [...check.reasons] : [];
|
|
422
370
|
if (check.canStart) {
|
|
423
371
|
if (check.oneAtATime && toolProcessingCount > 0) {
|
|
424
|
-
// One-at-a-time for this tool with a task already processing: skip it
|
|
425
|
-
// but don't block other tools.
|
|
372
|
+
// One-at-a-time for this tool with a task already processing: skip it but don't block other tools.
|
|
426
373
|
blockReasons.push(`one-at-a-time: ${toolProcessingCount} ${tool} task(s) already processing`);
|
|
427
374
|
} else {
|
|
428
375
|
startable = true;
|
|
429
376
|
startableItems.push({ item, tool, index: 0, check });
|
|
430
377
|
}
|
|
431
378
|
}
|
|
432
|
-
|
|
433
379
|
headDiagnostics.push({ tool, item, ageMs: Date.now() - item.createdAt, startable, blockReasons });
|
|
434
380
|
}
|
|
435
|
-
|
|
436
381
|
// Global FIFO: the oldest startable head wins the (globally paced) startup slot.
|
|
437
382
|
startableItems.sort((a, b) => a.item.createdAt - b.item.createdAt);
|
|
438
383
|
const selected = startableItems.slice(0, 1);
|
|
439
|
-
|
|
440
384
|
// Observe the dequeue decision (issue #2051): see reportDequeueDecision().
|
|
441
385
|
reportDequeueDecision(this, headDiagnostics, selected[0]);
|
|
442
|
-
|
|
443
386
|
return selected;
|
|
444
387
|
}
|
|
445
|
-
|
|
446
388
|
/**
|
|
447
389
|
* Reject all items in a tool queue and notify users.
|
|
448
390
|
* Called when a 'reject' strategy threshold is exceeded for queued items.
|
|
@@ -459,14 +401,11 @@ export class SolveQueue {
|
|
|
459
401
|
item.setFailed(reason);
|
|
460
402
|
this.failed.push(item);
|
|
461
403
|
this.stats.totalFailed++;
|
|
462
|
-
|
|
463
404
|
this.log(`Rejected queued item: ${item.toString()} from ${tool} queue - ${reason}`);
|
|
464
|
-
|
|
465
405
|
await this.updateItemMessage(item, t('telegram.solve_rejected', { infoBlock: item.infoBlock, reason }, { locale: item.locale }));
|
|
466
406
|
}
|
|
467
407
|
while (this.failed.length > 100) this.failed.shift();
|
|
468
408
|
}
|
|
469
|
-
|
|
470
409
|
/**
|
|
471
410
|
* Find first queue item that can start based on its tool's limits (legacy compatibility)
|
|
472
411
|
* With separate queues, returns the first startable item from any tool queue.
|
|
@@ -482,7 +421,6 @@ export class SolveQueue {
|
|
|
482
421
|
}
|
|
483
422
|
return { item: null, index: -1, check: null };
|
|
484
423
|
}
|
|
485
|
-
|
|
486
424
|
/**
|
|
487
425
|
* Get queue items summary for display
|
|
488
426
|
* Combines items from all tool queues into a single pending list.
|
|
@@ -506,10 +444,8 @@ export class SolveQueue {
|
|
|
506
444
|
});
|
|
507
445
|
}
|
|
508
446
|
}
|
|
509
|
-
|
|
510
447
|
// Sort by createdAt to show oldest first (global order)
|
|
511
448
|
pending.sort((a, b) => a.createdAt - b.createdAt);
|
|
512
|
-
|
|
513
449
|
return {
|
|
514
450
|
pending,
|
|
515
451
|
processing: Array.from(this.processing.values()).map(item => ({
|
|
@@ -522,7 +458,6 @@ export class SolveQueue {
|
|
|
522
458
|
})),
|
|
523
459
|
};
|
|
524
460
|
}
|
|
525
|
-
|
|
526
461
|
/**
|
|
527
462
|
* Get external processing counts from both process scanning and tracked
|
|
528
463
|
* isolated sessions. The displayed/accounted value is the maximum of the two
|
|
@@ -539,7 +474,6 @@ export class SolveQueue {
|
|
|
539
474
|
const isolatedByTool = isolated.byTool || {};
|
|
540
475
|
const processByTool = {};
|
|
541
476
|
const byTool = {};
|
|
542
|
-
|
|
543
477
|
await Promise.all(
|
|
544
478
|
uniqueTools.map(async tool => {
|
|
545
479
|
const result = await this.getRunningProcessesFn(tool, this.verbose);
|
|
@@ -549,10 +483,8 @@ export class SolveQueue {
|
|
|
549
483
|
byTool[tool] = Math.max(processCount, isolatedCount);
|
|
550
484
|
})
|
|
551
485
|
);
|
|
552
|
-
|
|
553
486
|
const processTotal = Object.values(processByTool).reduce((sum, count) => sum + count, 0);
|
|
554
487
|
const isolatedTotal = isolated.count || Object.values(isolatedByTool).reduce((sum, count) => sum + count, 0);
|
|
555
|
-
|
|
556
488
|
return {
|
|
557
489
|
byTool,
|
|
558
490
|
processByTool,
|
|
@@ -562,7 +494,6 @@ export class SolveQueue {
|
|
|
562
494
|
processTotal,
|
|
563
495
|
};
|
|
564
496
|
}
|
|
565
|
-
|
|
566
497
|
/**
|
|
567
498
|
* Check if a new command can start.
|
|
568
499
|
*
|
|
@@ -586,10 +517,7 @@ export class SolveQueue {
|
|
|
586
517
|
let oneAtATime = false;
|
|
587
518
|
let rejected = false;
|
|
588
519
|
let rejectReason = null;
|
|
589
|
-
|
|
590
|
-
// Check minimum interval since the last task start globally.
|
|
591
|
-
// Issue #2015: do not let another tool queue bypass startup pacing; host
|
|
592
|
-
// CPU/RAM/disk metrics need time to settle before any next task starts.
|
|
520
|
+
// Check minimum interval since the last task start globally. Issue #2015: do not let another tool queue bypass startup pacing; host CPU/RAM/disk metrics need time to settle before any next task starts.
|
|
593
521
|
const lastStartTime = this.lastStartTime || null;
|
|
594
522
|
if (lastStartTime) {
|
|
595
523
|
const timeSinceLastStart = Date.now() - lastStartTime;
|
|
@@ -599,7 +527,6 @@ export class SolveQueue {
|
|
|
599
527
|
this.recordThrottle('min_interval');
|
|
600
528
|
}
|
|
601
529
|
}
|
|
602
|
-
|
|
603
530
|
// Check running tool processes (this is a metric, not a blocking reason by itself).
|
|
604
531
|
// For screen-isolated sessions, use the maximum of `$ --status` executing
|
|
605
532
|
// counts and pgrep counts so detached sessions remain visible.
|
|
@@ -613,11 +540,9 @@ export class SolveQueue {
|
|
|
613
540
|
const hasRunningCodex = codexProcessCount > 0;
|
|
614
541
|
const hasRunningQwen = qwenProcessCount > 0;
|
|
615
542
|
const hasRunningGemini = geminiProcessCount > 0;
|
|
616
|
-
|
|
617
543
|
// Calculate total processing count for system resources (all tools)
|
|
618
544
|
// System resources (RAM, CPU, disk) apply to all tools
|
|
619
545
|
const totalProcessing = this.processing.size + externalProcessing.total;
|
|
620
|
-
|
|
621
546
|
// Calculate Claude-specific processing count for Claude API limits
|
|
622
547
|
// Only counts Claude items in queue + external claude processes
|
|
623
548
|
// Non-Claude items don't count against Claude's one-at-a-time limit
|
|
@@ -626,7 +551,6 @@ export class SolveQueue {
|
|
|
626
551
|
const codexProcessingCount = this.getProcessingCountByTool('codex');
|
|
627
552
|
const qwenProcessingCount = this.getProcessingCountByTool('qwen');
|
|
628
553
|
const geminiProcessingCount = this.getProcessingCountByTool('gemini');
|
|
629
|
-
|
|
630
554
|
// Track claude_running as a metric (but don't add to reasons yet)
|
|
631
555
|
if (hasRunningClaude) {
|
|
632
556
|
this.recordThrottle('claude_running');
|
|
@@ -640,7 +564,6 @@ export class SolveQueue {
|
|
|
640
564
|
if (hasRunningGemini) {
|
|
641
565
|
this.recordThrottle('gemini_running');
|
|
642
566
|
}
|
|
643
|
-
|
|
644
567
|
// Check system resources with strategy support
|
|
645
568
|
// System resources apply to ALL tools, not just Claude
|
|
646
569
|
// See: https://github.com/link-assistant/hive-mind/issues/1155
|
|
@@ -656,7 +579,6 @@ export class SolveQueue {
|
|
|
656
579
|
if (resourceCheck.oneAtATime) {
|
|
657
580
|
oneAtATime = true;
|
|
658
581
|
}
|
|
659
|
-
|
|
660
582
|
// Check API limits with strategy support (pass hasRunningClaude, claudeProcessingCount, and tool)
|
|
661
583
|
// Claude limits use claudeProcessingCount (only Claude items), not totalProcessing
|
|
662
584
|
// This allows non-Claude tasks to proceed when Claude limits are reached
|
|
@@ -675,7 +597,6 @@ export class SolveQueue {
|
|
|
675
597
|
if (limitCheck.oneAtATime) {
|
|
676
598
|
oneAtATime = true;
|
|
677
599
|
}
|
|
678
|
-
|
|
679
600
|
// "Claude process running" only blocks if there are OTHER reasons too
|
|
680
601
|
// This allows parallel execution when limits are not exceeded
|
|
681
602
|
if (hasRunningClaude && reasons.length > 0) {
|
|
@@ -693,9 +614,7 @@ export class SolveQueue {
|
|
|
693
614
|
if (tool === 'gemini' && hasRunningGemini && reasons.length > 0) {
|
|
694
615
|
reasons.push(`${formatWaitingReason('gemini_running', geminiProcessCount, 0, { locale })} (${lt('queue_processes', { count: geminiProcessCount }, { locale })})`);
|
|
695
616
|
}
|
|
696
|
-
|
|
697
617
|
const canStart = reasons.length === 0 && !rejected;
|
|
698
|
-
|
|
699
618
|
if (!canStart && this.verbose) {
|
|
700
619
|
if (rejected) {
|
|
701
620
|
this.log(`Rejected: ${rejectReason}`);
|
|
@@ -703,7 +622,6 @@ export class SolveQueue {
|
|
|
703
622
|
this.log(`Cannot start: ${reasons.join(', ')}`);
|
|
704
623
|
}
|
|
705
624
|
}
|
|
706
|
-
|
|
707
625
|
return {
|
|
708
626
|
canStart,
|
|
709
627
|
rejected,
|
|
@@ -724,7 +642,6 @@ export class SolveQueue {
|
|
|
724
642
|
geminiProcessingCount,
|
|
725
643
|
};
|
|
726
644
|
}
|
|
727
|
-
|
|
728
645
|
/**
|
|
729
646
|
* Check system resources (RAM, CPU, disk) using cached values
|
|
730
647
|
*
|
|
@@ -755,7 +672,6 @@ export class SolveQueue {
|
|
|
755
672
|
let oneAtATime = false;
|
|
756
673
|
let rejected = false;
|
|
757
674
|
let rejectReason = null;
|
|
758
|
-
|
|
759
675
|
// Check RAM (using cached value)
|
|
760
676
|
const memResult = await getCachedMemoryInfo(this.verbose);
|
|
761
677
|
if (memResult.success) {
|
|
@@ -764,7 +680,6 @@ export class SolveQueue {
|
|
|
764
680
|
const reason = formatWaitingReason('ram', memResult.memory.usedPercentage, QUEUE_CONFIG.thresholds.ram.value, { locale });
|
|
765
681
|
const strategy = QUEUE_CONFIG.thresholds.ram.strategy;
|
|
766
682
|
this.recordThrottle(`ram_${strategy}`);
|
|
767
|
-
|
|
768
683
|
if (strategy === 'reject') {
|
|
769
684
|
rejected = true;
|
|
770
685
|
rejectReason = reason;
|
|
@@ -779,7 +694,6 @@ export class SolveQueue {
|
|
|
779
694
|
}
|
|
780
695
|
}
|
|
781
696
|
}
|
|
782
|
-
|
|
783
697
|
// Check CPU using 5-minute load average (more stable than 1-minute)
|
|
784
698
|
const cpuResult = await getCachedCpuInfo(this.verbose);
|
|
785
699
|
if (cpuResult.success) {
|
|
@@ -791,16 +705,13 @@ export class SolveQueue {
|
|
|
791
705
|
// Load average of 1.0 per CPU = 100% utilization
|
|
792
706
|
const usageRatio = loadAvg5 / cpuCount;
|
|
793
707
|
const usagePercent = Math.min(100, Math.round(usageRatio * 100));
|
|
794
|
-
|
|
795
708
|
if (this.verbose) {
|
|
796
709
|
this.log(`CPU 5m load avg: ${loadAvg5.toFixed(2)}, cpus: ${cpuCount}, usage: ${usagePercent}%`);
|
|
797
710
|
}
|
|
798
|
-
|
|
799
711
|
if (usageRatio >= QUEUE_CONFIG.thresholds.cpu.value) {
|
|
800
712
|
const reason = formatWaitingReason('cpu', usagePercent, QUEUE_CONFIG.thresholds.cpu.value, { locale });
|
|
801
713
|
const strategy = QUEUE_CONFIG.thresholds.cpu.strategy;
|
|
802
714
|
this.recordThrottle(`cpu_${strategy}`);
|
|
803
|
-
|
|
804
715
|
if (strategy === 'reject') {
|
|
805
716
|
rejected = true;
|
|
806
717
|
rejectReason = reason;
|
|
@@ -815,7 +726,6 @@ export class SolveQueue {
|
|
|
815
726
|
}
|
|
816
727
|
}
|
|
817
728
|
}
|
|
818
|
-
|
|
819
729
|
// Check disk space (using cached value)
|
|
820
730
|
// Default strategy changed to 'reject' because queue is lost on restart anyway
|
|
821
731
|
// See: https://github.com/link-assistant/hive-mind/issues/1253
|
|
@@ -828,7 +738,6 @@ export class SolveQueue {
|
|
|
828
738
|
const reason = formatWaitingReason('disk', usedPercent, QUEUE_CONFIG.thresholds.disk.value, { locale });
|
|
829
739
|
const strategy = QUEUE_CONFIG.thresholds.disk.strategy;
|
|
830
740
|
this.recordThrottle(`disk_${strategy}`);
|
|
831
|
-
|
|
832
741
|
if (strategy === 'reject') {
|
|
833
742
|
rejected = true;
|
|
834
743
|
rejectReason = reason;
|
|
@@ -843,10 +752,8 @@ export class SolveQueue {
|
|
|
843
752
|
}
|
|
844
753
|
}
|
|
845
754
|
}
|
|
846
|
-
|
|
847
755
|
return { ok: reasons.length === 0 && !rejected, reasons, oneAtATime, rejected, rejectReason };
|
|
848
756
|
}
|
|
849
|
-
|
|
850
757
|
/**
|
|
851
758
|
* Check API limits (Claude, GitHub) using cached values
|
|
852
759
|
*
|
|
@@ -876,16 +783,13 @@ export class SolveQueue {
|
|
|
876
783
|
let oneAtATime = false;
|
|
877
784
|
let rejected = false;
|
|
878
785
|
let rejectReason = null;
|
|
879
|
-
|
|
880
786
|
// Apply Claude-specific limits only when tool is 'claude'
|
|
881
787
|
// Other tools (like 'agent', 'gemini', and 'qwen') use different rate limiting backends and are not
|
|
882
788
|
// affected by Claude API limits (5-hour session, weekly limits)
|
|
883
789
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
884
790
|
const applyClaudeLimits = tool === 'claude';
|
|
885
791
|
const applyCodexLimits = tool === 'codex';
|
|
886
|
-
|
|
887
792
|
const totalToolProcessing = toolProcessingCount + (hasRunningToolProcess ? 1 : 0);
|
|
888
|
-
|
|
889
793
|
// Check Claude limits (using cached value)
|
|
890
794
|
// Only applied when tool is 'claude'
|
|
891
795
|
if (applyClaudeLimits) {
|
|
@@ -893,7 +797,6 @@ export class SolveQueue {
|
|
|
893
797
|
if (claudeResult.success) {
|
|
894
798
|
const sessionPercent = claudeResult.usage.currentSession.percentage;
|
|
895
799
|
const weeklyPercent = claudeResult.usage.allModels.percentage;
|
|
896
|
-
|
|
897
800
|
// Session limit (5-hour)
|
|
898
801
|
// Configurable strategy via HIVE_MIND_QUEUE_CONFIG or HIVE_MIND_CLAUDE_5_HOUR_SESSION_STRATEGY
|
|
899
802
|
// See: https://github.com/link-assistant/hive-mind/issues/1133, #1159, #1253
|
|
@@ -903,7 +806,6 @@ export class SolveQueue {
|
|
|
903
806
|
const reason = formatWaitingReason('claude_5_hour_session', sessionPercent, QUEUE_CONFIG.thresholds.claude5Hour.value, { locale });
|
|
904
807
|
const strategy = QUEUE_CONFIG.thresholds.claude5Hour.strategy;
|
|
905
808
|
this.recordThrottle(sessionRatio >= 1.0 ? 'claude_5_hour_session_100' : `claude_5_hour_session_${strategy}`);
|
|
906
|
-
|
|
907
809
|
if (strategy === 'reject') {
|
|
908
810
|
rejected = true;
|
|
909
811
|
rejectReason = reason;
|
|
@@ -918,7 +820,6 @@ export class SolveQueue {
|
|
|
918
820
|
}
|
|
919
821
|
}
|
|
920
822
|
}
|
|
921
|
-
|
|
922
823
|
// Weekly limit
|
|
923
824
|
// Configurable strategy via HIVE_MIND_QUEUE_CONFIG or HIVE_MIND_CLAUDE_WEEKLY_STRATEGY
|
|
924
825
|
// See: https://github.com/link-assistant/hive-mind/issues/1133, #1159, #1253
|
|
@@ -928,7 +829,6 @@ export class SolveQueue {
|
|
|
928
829
|
const reason = formatWaitingReason('claude_weekly', weeklyPercent, QUEUE_CONFIG.thresholds.claudeWeekly.value, { locale });
|
|
929
830
|
const strategy = QUEUE_CONFIG.thresholds.claudeWeekly.strategy;
|
|
930
831
|
this.recordThrottle(weeklyRatio >= 1.0 ? 'claude_weekly_100' : `claude_weekly_${strategy}`);
|
|
931
|
-
|
|
932
832
|
if (strategy === 'reject') {
|
|
933
833
|
rejected = true;
|
|
934
834
|
rejectReason = reason;
|
|
@@ -949,14 +849,12 @@ export class SolveQueue {
|
|
|
949
849
|
if (codexResult.success) {
|
|
950
850
|
const sessionPercent = codexResult.usage.currentSession.percentage;
|
|
951
851
|
const weeklyPercent = codexResult.usage.allModels.percentage;
|
|
952
|
-
|
|
953
852
|
if (sessionPercent !== null) {
|
|
954
853
|
const sessionRatio = sessionPercent / 100;
|
|
955
854
|
if (sessionRatio >= QUEUE_CONFIG.thresholds.codex5Hour.value) {
|
|
956
855
|
const reason = formatWaitingReason('codex_5_hour_session', sessionPercent, QUEUE_CONFIG.thresholds.codex5Hour.value, { locale });
|
|
957
856
|
const strategy = QUEUE_CONFIG.thresholds.codex5Hour.strategy;
|
|
958
857
|
this.recordThrottle(sessionRatio >= 1.0 ? 'codex_5_hour_session_100' : `codex_5_hour_session_${strategy}`);
|
|
959
|
-
|
|
960
858
|
if (strategy === 'reject') {
|
|
961
859
|
rejected = true;
|
|
962
860
|
rejectReason = reason;
|
|
@@ -970,14 +868,12 @@ export class SolveQueue {
|
|
|
970
868
|
}
|
|
971
869
|
}
|
|
972
870
|
}
|
|
973
|
-
|
|
974
871
|
if (weeklyPercent !== null) {
|
|
975
872
|
const weeklyRatio = weeklyPercent / 100;
|
|
976
873
|
if (weeklyRatio >= QUEUE_CONFIG.thresholds.codexWeekly.value) {
|
|
977
874
|
const reason = formatWaitingReason('codex_weekly', weeklyPercent, QUEUE_CONFIG.thresholds.codexWeekly.value, { locale });
|
|
978
875
|
const strategy = QUEUE_CONFIG.thresholds.codexWeekly.strategy;
|
|
979
876
|
this.recordThrottle(weeklyRatio >= 1.0 ? 'codex_weekly_100' : `codex_weekly_${strategy}`);
|
|
980
|
-
|
|
981
877
|
if (strategy === 'reject') {
|
|
982
878
|
rejected = true;
|
|
983
879
|
rejectReason = reason;
|
|
@@ -995,7 +891,6 @@ export class SolveQueue {
|
|
|
995
891
|
} else if (this.verbose) {
|
|
996
892
|
this.log(`Claude limits not applied for --tool ${tool}`);
|
|
997
893
|
}
|
|
998
|
-
|
|
999
894
|
// Check GitHub limits when the active tool already has a running process.
|
|
1000
895
|
// This keeps the queue behavior aligned with the existing one-at-a-time throttling model.
|
|
1001
896
|
// Configurable strategy via HIVE_MIND_QUEUE_CONFIG or HIVE_MIND_GITHUB_API_STRATEGY
|
|
@@ -1008,7 +903,6 @@ export class SolveQueue {
|
|
|
1008
903
|
const reason = formatWaitingReason('github', usedPercent, QUEUE_CONFIG.thresholds.githubApi.value, { locale });
|
|
1009
904
|
const strategy = QUEUE_CONFIG.thresholds.githubApi.strategy;
|
|
1010
905
|
this.recordThrottle(usedRatio >= 1.0 ? 'github_100' : `github_${strategy}`);
|
|
1011
|
-
|
|
1012
906
|
if (strategy === 'reject') {
|
|
1013
907
|
rejected = true;
|
|
1014
908
|
rejectReason = reason;
|
|
@@ -1024,10 +918,8 @@ export class SolveQueue {
|
|
|
1024
918
|
}
|
|
1025
919
|
}
|
|
1026
920
|
}
|
|
1027
|
-
|
|
1028
921
|
return { ok: reasons.length === 0 && !rejected, reasons, oneAtATime, rejected, rejectReason };
|
|
1029
922
|
}
|
|
1030
|
-
|
|
1031
923
|
/**
|
|
1032
924
|
* Record a throttle event for statistics
|
|
1033
925
|
* @param {string} reason
|
|
@@ -1035,20 +927,17 @@ export class SolveQueue {
|
|
|
1035
927
|
recordThrottle(reason) {
|
|
1036
928
|
this.stats.throttleReasons[reason] = (this.stats.throttleReasons[reason] || 0) + 1;
|
|
1037
929
|
}
|
|
1038
|
-
|
|
1039
930
|
/**
|
|
1040
931
|
* Ensure consumer task is running
|
|
1041
932
|
*/
|
|
1042
933
|
ensureConsumerRunning() {
|
|
1043
934
|
if (this.consumerTask) return;
|
|
1044
|
-
|
|
1045
935
|
this.consumerTask = this.runConsumer();
|
|
1046
936
|
this.consumerTask.catch(error => {
|
|
1047
937
|
console.error('[solve_queue] Consumer error:', error);
|
|
1048
938
|
this.consumerTask = null;
|
|
1049
939
|
});
|
|
1050
940
|
}
|
|
1051
|
-
|
|
1052
941
|
/**
|
|
1053
942
|
* Update item message in Telegram
|
|
1054
943
|
* @param {SolveQueueItem} item
|
|
@@ -1057,7 +946,6 @@ export class SolveQueue {
|
|
|
1057
946
|
*/
|
|
1058
947
|
async updateItemMessage(item, text, trackUpdateTime = true) {
|
|
1059
948
|
if (!item.messageInfo || !item.ctx) return;
|
|
1060
|
-
|
|
1061
949
|
try {
|
|
1062
950
|
const { chatId, messageId } = item.messageInfo;
|
|
1063
951
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, text, { parse_mode: 'Markdown' });
|
|
@@ -1068,7 +956,6 @@ export class SolveQueue {
|
|
|
1068
956
|
this.log(`Failed to update message: ${error.message}`);
|
|
1069
957
|
}
|
|
1070
958
|
}
|
|
1071
|
-
|
|
1072
959
|
/**
|
|
1073
960
|
* Check if an item's message should be updated periodically
|
|
1074
961
|
* @param {SolveQueueItem} item
|
|
@@ -1079,7 +966,6 @@ export class SolveQueue {
|
|
|
1079
966
|
if (!item.lastMessageUpdateTime) return true; // Never updated
|
|
1080
967
|
return Date.now() - item.lastMessageUpdateTime >= QUEUE_CONFIG.MESSAGE_UPDATE_INTERVAL_MS;
|
|
1081
968
|
}
|
|
1082
|
-
|
|
1083
969
|
/**
|
|
1084
970
|
* Consumer loop - processes items from all tool queues
|
|
1085
971
|
*
|
|
@@ -1093,16 +979,13 @@ export class SolveQueue {
|
|
|
1093
979
|
*/
|
|
1094
980
|
async runConsumer() {
|
|
1095
981
|
this.log('Consumer started with separate tool queues');
|
|
1096
|
-
|
|
1097
982
|
while (this.isRunning) {
|
|
1098
983
|
// Check if all queues are empty
|
|
1099
984
|
if (this.getTotalQueueLength() === 0) {
|
|
1100
985
|
await this.sleep(QUEUE_CONFIG.CONSUMER_POLL_INTERVAL_MS);
|
|
1101
986
|
continue;
|
|
1102
987
|
}
|
|
1103
|
-
|
|
1104
988
|
const startableItems = await this.findStartableItems();
|
|
1105
|
-
|
|
1106
989
|
if (startableItems.length === 0) {
|
|
1107
990
|
// No items can start - update all queued items with their tool-specific waiting reasons
|
|
1108
991
|
await this.updateAllWaitingItems();
|
|
@@ -1110,37 +993,28 @@ export class SolveQueue {
|
|
|
1110
993
|
await this.sleep(QUEUE_CONFIG.CONSUMER_POLL_INTERVAL_MS);
|
|
1111
994
|
continue;
|
|
1112
995
|
}
|
|
1113
|
-
|
|
1114
996
|
for (const startable of startableItems) {
|
|
1115
997
|
const { tool } = startable;
|
|
1116
998
|
const toolQueue = this.getToolQueue(tool);
|
|
1117
|
-
|
|
1118
999
|
// Remove the first item from this tool's queue
|
|
1119
1000
|
const item = toolQueue.shift();
|
|
1120
1001
|
if (!item) continue;
|
|
1121
|
-
|
|
1122
1002
|
// Update status to Starting
|
|
1123
1003
|
item.setStarting();
|
|
1124
1004
|
this.processing.set(item.id, item);
|
|
1125
|
-
|
|
1126
1005
|
this.recordStart(tool);
|
|
1127
1006
|
this.stats.totalStarted++;
|
|
1128
|
-
|
|
1129
1007
|
await this.updateItemMessage(item, formatStartingWorkSessionMessage({ infoBlock: item.infoBlock, locale: item.locale }));
|
|
1130
|
-
|
|
1131
1008
|
this.log(`Starting: ${item.toString()} from ${tool} queue`);
|
|
1132
|
-
|
|
1133
1009
|
// Execute in background
|
|
1134
1010
|
this.executeItem(item).catch(error => {
|
|
1135
1011
|
console.error(`[solve_queue] Execution error for ${item.id}:`, error);
|
|
1136
1012
|
});
|
|
1137
1013
|
}
|
|
1138
1014
|
}
|
|
1139
|
-
|
|
1140
1015
|
this.log('Consumer stopped');
|
|
1141
1016
|
this.consumerTask = null;
|
|
1142
1017
|
}
|
|
1143
|
-
|
|
1144
1018
|
/**
|
|
1145
1019
|
* Update all waiting items with their tool-specific waiting reasons.
|
|
1146
1020
|
* Items blocked by a 'reject' strategy threshold are immediately rejected
|
|
@@ -1160,7 +1034,6 @@ export class SolveQueue {
|
|
|
1160
1034
|
await this.rejectAllItemsInQueue(tool, toolQueue, toolCheck.rejectReason);
|
|
1161
1035
|
continue;
|
|
1162
1036
|
}
|
|
1163
|
-
|
|
1164
1037
|
for (let i = 0; i < toolQueue.length; i++) {
|
|
1165
1038
|
const item = toolQueue[i];
|
|
1166
1039
|
if (item.status === QueueItemStatus.QUEUED || item.status === QueueItemStatus.WAITING) {
|
|
@@ -1169,10 +1042,8 @@ export class SolveQueue {
|
|
|
1169
1042
|
const previousReason = item.waitingReason;
|
|
1170
1043
|
const waitReason = itemCheck.reason || lt('queue_waiting_in_queue', {}, { locale: item.locale });
|
|
1171
1044
|
item.setWaiting(waitReason);
|
|
1172
|
-
|
|
1173
1045
|
// Update message if status/reason changed or it's time for periodic update
|
|
1174
1046
|
const shouldUpdate = previousStatus !== item.status || previousReason !== item.waitingReason || this.shouldUpdateMessage(item);
|
|
1175
|
-
|
|
1176
1047
|
if (shouldUpdate) {
|
|
1177
1048
|
const position = i + 1; // Position within this tool's queue
|
|
1178
1049
|
await this.updateItemMessage(item, `${t('telegram.solve_waiting', { tool, position }, { locale: item.locale })}\n\n${item.infoBlock}\n\n*${t('telegram.reason_label', {}, { locale: item.locale })}:*\n${item.waitingReason}`);
|
|
@@ -1181,7 +1052,6 @@ export class SolveQueue {
|
|
|
1181
1052
|
}
|
|
1182
1053
|
}
|
|
1183
1054
|
}
|
|
1184
|
-
|
|
1185
1055
|
/**
|
|
1186
1056
|
* Execute a queue item
|
|
1187
1057
|
* @param {SolveQueueItem} item
|
|
@@ -1190,23 +1060,19 @@ export class SolveQueue {
|
|
|
1190
1060
|
try {
|
|
1191
1061
|
if (this.executeCallback) {
|
|
1192
1062
|
const result = await this.executeCallback(item);
|
|
1193
|
-
|
|
1194
1063
|
// Extract session name from result
|
|
1195
1064
|
let sessionName = result?.sessionId || 'unknown';
|
|
1196
1065
|
if (result && result.output) {
|
|
1197
1066
|
const sessionMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/);
|
|
1198
1067
|
if (sessionMatch) sessionName = sessionMatch[1];
|
|
1199
1068
|
}
|
|
1200
|
-
|
|
1201
1069
|
// IMPORTANT: Save messageInfo BEFORE calling setStarted, because setStarted clears it
|
|
1202
1070
|
// This was a bug where the final message update never happened because messageInfo was null
|
|
1203
1071
|
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
1204
1072
|
const savedMessageInfo = item.messageInfo;
|
|
1205
|
-
|
|
1206
1073
|
// Update to Started status (terminal - forgets message tracking)
|
|
1207
1074
|
item.setStarted(sessionName);
|
|
1208
1075
|
this.stats.totalCompleted++;
|
|
1209
|
-
|
|
1210
1076
|
// Final message update using saved messageInfo
|
|
1211
1077
|
if (item.ctx && result && savedMessageInfo) {
|
|
1212
1078
|
const { chatId, messageId } = savedMessageInfo;
|
|
@@ -1241,7 +1107,6 @@ export class SolveQueue {
|
|
|
1241
1107
|
item.setFailed(error);
|
|
1242
1108
|
this.stats.totalFailed++;
|
|
1243
1109
|
console.error(`[solve_queue] Item failed: ${item.id}`, error);
|
|
1244
|
-
|
|
1245
1110
|
// Try to update message with error
|
|
1246
1111
|
const { chatId, messageId } = item.messageInfo || {};
|
|
1247
1112
|
if (chatId && messageId && item.ctx) {
|
|
@@ -1256,21 +1121,17 @@ export class SolveQueue {
|
|
|
1256
1121
|
}
|
|
1257
1122
|
} finally {
|
|
1258
1123
|
this.processing.delete(item.id);
|
|
1259
|
-
|
|
1260
1124
|
if (item.status === QueueItemStatus.STARTED) {
|
|
1261
1125
|
this.completed.push(item);
|
|
1262
1126
|
} else if (item.status === QueueItemStatus.FAILED) {
|
|
1263
1127
|
this.failed.push(item);
|
|
1264
1128
|
}
|
|
1265
|
-
|
|
1266
1129
|
this.log(`Finished: ${item.toString()}`);
|
|
1267
|
-
|
|
1268
1130
|
// Limit history size
|
|
1269
1131
|
while (this.completed.length > 100) this.completed.shift();
|
|
1270
1132
|
while (this.failed.length > 100) this.failed.shift();
|
|
1271
1133
|
}
|
|
1272
1134
|
}
|
|
1273
|
-
|
|
1274
1135
|
/**
|
|
1275
1136
|
* Sleep for specified milliseconds
|
|
1276
1137
|
* @param {number} ms
|
|
@@ -1279,7 +1140,6 @@ export class SolveQueue {
|
|
|
1279
1140
|
sleep(ms) {
|
|
1280
1141
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1281
1142
|
}
|
|
1282
|
-
|
|
1283
1143
|
/**
|
|
1284
1144
|
* Stop the queue
|
|
1285
1145
|
*/
|
|
@@ -1287,7 +1147,6 @@ export class SolveQueue {
|
|
|
1287
1147
|
this.log('Stopping queue...');
|
|
1288
1148
|
this.isRunning = false;
|
|
1289
1149
|
}
|
|
1290
|
-
|
|
1291
1150
|
/**
|
|
1292
1151
|
* Clear the limit cache
|
|
1293
1152
|
*/
|
|
@@ -1295,7 +1154,6 @@ export class SolveQueue {
|
|
|
1295
1154
|
getLimitCache().clear();
|
|
1296
1155
|
this.log('Limit cache cleared');
|
|
1297
1156
|
}
|
|
1298
|
-
|
|
1299
1157
|
/**
|
|
1300
1158
|
* Format queue status for display in /limits command
|
|
1301
1159
|
* Shows per-tool queue breakdown with processing counts.
|
|
@@ -1324,10 +1182,8 @@ export class SolveQueue {
|
|
|
1324
1182
|
const processing = externalProcessing.byTool[tool] || 0;
|
|
1325
1183
|
message += `${tool} (${lt('queue_pending', {}, { locale })}: ${pending}, ${lt('queue_processing', {}, { locale })}: ${processing})\n`;
|
|
1326
1184
|
}
|
|
1327
|
-
|
|
1328
1185
|
return message;
|
|
1329
1186
|
}
|
|
1330
|
-
|
|
1331
1187
|
/**
|
|
1332
1188
|
* Format detailed queue status for Telegram message.
|
|
1333
1189
|
*
|
|
@@ -1349,7 +1205,6 @@ export class SolveQueue {
|
|
|
1349
1205
|
// real running tasks; the queue's own `processing` Map is emptied once a task
|
|
1350
1206
|
// is dispatched, so without this the executing items are never listed (#1837).
|
|
1351
1207
|
const runningSessionItems = await this.getRunningSessionItemsFn(this.verbose);
|
|
1352
|
-
|
|
1353
1208
|
// Section labels: each per-tool sub-list uses a capitalized heading.
|
|
1354
1209
|
const cap = s => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);
|
|
1355
1210
|
const labels = {
|
|
@@ -1358,19 +1213,15 @@ export class SolveQueue {
|
|
|
1358
1213
|
completed: cap(lt('queue_completed', {}, { locale })),
|
|
1359
1214
|
failed: cap(lt('queue_failed', {}, { locale })),
|
|
1360
1215
|
};
|
|
1361
|
-
|
|
1362
1216
|
// Group the (globally-capped) completed/failed history by tool so each tool
|
|
1363
1217
|
// queue shows its own Completed/Failed list (issue #1891 follow-up).
|
|
1364
1218
|
const completedByTool = groupQueueItemsByTool(this.completed);
|
|
1365
1219
|
const failedByTool = groupQueueItemsByTool(this.failed);
|
|
1366
|
-
|
|
1367
1220
|
let message = `📋 *${lt('solve_queue_status', {}, { locale })}*\n\n`;
|
|
1368
1221
|
const max = QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE;
|
|
1369
|
-
|
|
1370
1222
|
// Every tool with *any* activity (queued, processing, or in history) so
|
|
1371
1223
|
// per-tool history shows even after a tool's live queue has drained.
|
|
1372
1224
|
const tools = [...new Set([...Object.keys(this.queues), ...Object.keys(completedByTool), ...Object.keys(failedByTool)])];
|
|
1373
|
-
|
|
1374
1225
|
for (const tool of tools) {
|
|
1375
1226
|
const toolQueue = this.queues[tool] || [];
|
|
1376
1227
|
const pending = toolQueue.length;
|
|
@@ -1380,26 +1231,20 @@ export class SolveQueue {
|
|
|
1380
1231
|
const executing = collectExecutingItems({ processingItems: this.processing.values(), sessionItems: runningSessionItems, tool });
|
|
1381
1232
|
const completed = completedByTool[tool] || [];
|
|
1382
1233
|
const failed = failedByTool[tool] || [];
|
|
1383
|
-
|
|
1384
1234
|
// Skip tools with nothing to show in any list.
|
|
1385
1235
|
if (pending === 0 && executing.length === 0 && completed.length === 0 && failed.length === 0) continue;
|
|
1386
|
-
|
|
1387
1236
|
const pendingItems = toolQueue.map(item => ({ url: item.url, waitMs: item.getWaitTime(), waitingReason: item.waitingReason }));
|
|
1388
1237
|
message += formatQueueToolSection({ tool, executing, pendingItems, completed, failed, labels, max, locale });
|
|
1389
1238
|
}
|
|
1390
|
-
|
|
1391
1239
|
// Summary stats
|
|
1392
1240
|
message += `${lt('queue_completed', {}, { locale })}: ${stats.completed}, ${lt('queue_failed', {}, { locale })}: ${stats.failed}\n`;
|
|
1393
|
-
|
|
1394
1241
|
return message;
|
|
1395
1242
|
}
|
|
1396
1243
|
}
|
|
1397
|
-
|
|
1398
1244
|
/**
|
|
1399
1245
|
* Global queue instance (singleton)
|
|
1400
1246
|
*/
|
|
1401
1247
|
let globalQueue = null;
|
|
1402
|
-
|
|
1403
1248
|
/**
|
|
1404
1249
|
* Get or create the global solve queue instance
|
|
1405
1250
|
* @param {Object} options - Queue options
|
|
@@ -1413,7 +1258,6 @@ export function getSolveQueue(options = {}) {
|
|
|
1413
1258
|
}
|
|
1414
1259
|
return globalQueue;
|
|
1415
1260
|
}
|
|
1416
|
-
|
|
1417
1261
|
/**
|
|
1418
1262
|
* Reset the global queue (useful for testing)
|
|
1419
1263
|
*/
|
|
@@ -1423,7 +1267,6 @@ export function resetSolveQueue() {
|
|
|
1423
1267
|
globalQueue = null;
|
|
1424
1268
|
}
|
|
1425
1269
|
}
|
|
1426
|
-
|
|
1427
1270
|
/**
|
|
1428
1271
|
* Create an execute callback for the queue
|
|
1429
1272
|
* @param {Function} executeStartScreen - Function to execute start-screen command
|
|
@@ -1460,7 +1303,6 @@ export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
|
|
|
1460
1303
|
return result;
|
|
1461
1304
|
};
|
|
1462
1305
|
}
|
|
1463
|
-
|
|
1464
1306
|
/**
|
|
1465
1307
|
* Get count of tracked isolated sessions that are still executing according
|
|
1466
1308
|
* to `$ --status`. Queue display combines this with pgrep counts using max().
|
|
@@ -1479,7 +1321,6 @@ export async function getRunningIsolatedSessions(verbose = false) {
|
|
|
1479
1321
|
return { count: 0, sessions: [], byTool: {} };
|
|
1480
1322
|
}
|
|
1481
1323
|
}
|
|
1482
|
-
|
|
1483
1324
|
export default {
|
|
1484
1325
|
SolveQueue,
|
|
1485
1326
|
SolveQueueItem,
|