@link-assistant/hive-mind 1.56.6 ā 1.56.8
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 +14 -0
- package/package.json +2 -2
- package/src/agent.lib.mjs +31 -4
- package/src/auto-iteration-limits.lib.mjs +33 -0
- package/src/claude.lib.mjs +9 -4
- package/src/codex.lib.mjs +47 -5
- package/src/hive.config.lib.mjs +1 -1
- package/src/hive.mjs +3 -0
- package/src/isolation-runner.lib.mjs +86 -27
- package/src/models/index.mjs +17 -0
- package/src/opencode.lib.mjs +28 -6
- package/src/option-suggestions.lib.mjs +1 -0
- package/src/session-monitor.lib.mjs +161 -77
- package/src/solve.auto-continue.lib.mjs +14 -0
- package/src/solve.auto-merge.lib.mjs +91 -24
- package/src/solve.config.lib.mjs +25 -3
- package/src/solve.error-handlers.lib.mjs +1 -1
- package/src/solve.execution.lib.mjs +1 -1
- package/src/solve.mjs +12 -15
- package/src/solve.pre-pr-failure-notifier.lib.mjs +1 -1
- package/src/solve.results.lib.mjs +14 -8
- package/src/solve.watch.lib.mjs +14 -9
- package/src/telegram-bot.mjs +9 -9
- package/src/telegram-isolation.lib.mjs +2 -2
- package/src/telegram-solve-queue.lib.mjs +80 -34
- package/src/tool-retry.lib.mjs +118 -0
package/src/solve.mjs
CHANGED
|
@@ -32,17 +32,13 @@ const results = await import('./solve.results.lib.mjs');
|
|
|
32
32
|
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
|
|
33
33
|
const claudeLib = await import('./claude.lib.mjs');
|
|
34
34
|
const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
|
|
35
|
-
|
|
36
35
|
const githubLinking = await import('./github-linking.lib.mjs');
|
|
37
36
|
const { extractLinkedIssueNumber } = githubLinking;
|
|
38
|
-
|
|
39
37
|
const usageLimitLib = await import('./usage-limit.lib.mjs');
|
|
40
38
|
const { formatResetTimeWithRelative } = usageLimitLib;
|
|
41
|
-
|
|
42
39
|
const errorHandlers = await import('./solve.error-handlers.lib.mjs');
|
|
43
40
|
const { createUncaughtExceptionHandler, createUnhandledRejectionHandler, handleMainExecutionError, handleNoPrAvailableError } = errorHandlers;
|
|
44
41
|
const { notifyIssueAboutPrePullRequestFailure } = await import('./solve.pre-pr-failure-notifier.lib.mjs');
|
|
45
|
-
|
|
46
42
|
const watchLib = await import('./solve.watch.lib.mjs');
|
|
47
43
|
const { startWatchMode } = watchLib;
|
|
48
44
|
const { startAutoRestartUntilMergeable } = await import('./solve.auto-merge.lib.mjs');
|
|
@@ -62,7 +58,6 @@ const { postTrackedComment, USAGE_LIMIT_REACHED_MARKER } = await import('./tool-
|
|
|
62
58
|
const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
|
|
63
59
|
const { validateAndExitOnInvalidModel } = await import('./models/index.mjs');
|
|
64
60
|
const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
|
|
65
|
-
|
|
66
61
|
// Initialize log file early (before argument parsing) to capture all output
|
|
67
62
|
const logFile = await initializeLogFile(null);
|
|
68
63
|
// Log version and raw command IMMEDIATELY after log file initialization
|
|
@@ -183,6 +178,8 @@ if (!(await validateContinueOnlyOnFeedback(argv, isPrUrl, isIssueUrl))) {
|
|
|
183
178
|
// Validate model name EARLY - always runs regardless of --skip-tool-connection-check
|
|
184
179
|
const tool = argv.tool || 'claude';
|
|
185
180
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
181
|
+
if (argv.fallbackModel) await validateAndExitOnInvalidModel(argv.fallbackModel, tool, safeExit);
|
|
182
|
+
argv.originalModel ||= argv.model;
|
|
186
183
|
|
|
187
184
|
// Validate --plan-model if provided (Issue #1223)
|
|
188
185
|
if (argv.planModel) {
|
|
@@ -912,7 +909,7 @@ try {
|
|
|
912
909
|
await log(` ${claudeResumeCmd}`);
|
|
913
910
|
await log('');
|
|
914
911
|
} else if (argv.url) {
|
|
915
|
-
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForResume, model: argv.model, tempDir });
|
|
912
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForResume, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
|
|
916
913
|
await log(`š” To continue this ${toolForResume} session with solve:`);
|
|
917
914
|
await log('');
|
|
918
915
|
await log(` ${solveResumeCmd}`);
|
|
@@ -926,7 +923,7 @@ try {
|
|
|
926
923
|
try {
|
|
927
924
|
// Build Claude CLI resume command
|
|
928
925
|
const tool = argv.tool || 'claude';
|
|
929
|
-
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
926
|
+
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null;
|
|
930
927
|
const logUploadSuccess = await attachLogToGitHub({
|
|
931
928
|
logFile: getLogFile(),
|
|
932
929
|
targetType: 'pr',
|
|
@@ -942,7 +939,7 @@ try {
|
|
|
942
939
|
toolName: getToolDisplayName(argv.tool),
|
|
943
940
|
resumeCommand,
|
|
944
941
|
sessionId,
|
|
945
|
-
requestedModel: argv.model,
|
|
942
|
+
requestedModel: argv.originalModel || argv.model,
|
|
946
943
|
tool: argv.tool || 'claude',
|
|
947
944
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
948
945
|
resultModelUsage,
|
|
@@ -964,7 +961,7 @@ try {
|
|
|
964
961
|
const resetTime = global.limitResetTime;
|
|
965
962
|
// Build Claude CLI resume command
|
|
966
963
|
const tool = argv.tool || 'claude';
|
|
967
|
-
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
964
|
+
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null;
|
|
968
965
|
const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
|
|
969
966
|
// Format the reset time with relative time and UTC conversion if available
|
|
970
967
|
const timezone = global.limitTimezone || null;
|
|
@@ -992,7 +989,7 @@ try {
|
|
|
992
989
|
try {
|
|
993
990
|
// Build Claude CLI resume command (only for logging, not shown to users when auto-resume is enabled)
|
|
994
991
|
const tool = argv.tool || 'claude';
|
|
995
|
-
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
992
|
+
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null;
|
|
996
993
|
const logUploadSuccess = await attachLogToGitHub({
|
|
997
994
|
logFile: getLogFile(),
|
|
998
995
|
targetType: 'pr',
|
|
@@ -1012,7 +1009,7 @@ try {
|
|
|
1012
1009
|
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
1013
1010
|
isAutoResumeEnabled: true,
|
|
1014
1011
|
autoResumeMode: limitContinueMode,
|
|
1015
|
-
requestedModel: argv.model,
|
|
1012
|
+
requestedModel: argv.originalModel || argv.model,
|
|
1016
1013
|
tool: argv.tool || 'claude',
|
|
1017
1014
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
1018
1015
|
resultModelUsage,
|
|
@@ -1081,7 +1078,7 @@ try {
|
|
|
1081
1078
|
await log(` ${claudeResumeCmd}`);
|
|
1082
1079
|
await log('');
|
|
1083
1080
|
} else if (sessionId && argv.url) {
|
|
1084
|
-
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForFailure, model: argv.model, tempDir });
|
|
1081
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForFailure, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
|
|
1085
1082
|
await log('');
|
|
1086
1083
|
await log(`š” To continue this ${toolForFailure} session with solve:`);
|
|
1087
1084
|
await log('');
|
|
@@ -1101,7 +1098,7 @@ try {
|
|
|
1101
1098
|
try {
|
|
1102
1099
|
// Build Claude CLI resume command
|
|
1103
1100
|
const tool = argv.tool || 'claude';
|
|
1104
|
-
const resumeCommand = sessionId ? (tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir })) : null;
|
|
1101
|
+
const resumeCommand = sessionId ? (tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir })) : null;
|
|
1105
1102
|
const logUploadSuccess = await attachLogToGitHub({
|
|
1106
1103
|
logFile: getLogFile(),
|
|
1107
1104
|
targetType: logTargetType,
|
|
@@ -1120,7 +1117,7 @@ try {
|
|
|
1120
1117
|
sessionId,
|
|
1121
1118
|
// If not a usage limit case, fall back to generic failure format
|
|
1122
1119
|
errorMessage: limitReached ? undefined : `${argv.tool.toUpperCase()} execution failed`,
|
|
1123
|
-
requestedModel: argv.model,
|
|
1120
|
+
requestedModel: argv.originalModel || argv.model,
|
|
1124
1121
|
tool: argv.tool || 'claude',
|
|
1125
1122
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
1126
1123
|
resultModelUsage,
|
|
@@ -1383,7 +1380,7 @@ try {
|
|
|
1383
1380
|
sessionId,
|
|
1384
1381
|
tempDir,
|
|
1385
1382
|
anthropicTotalCostUSD,
|
|
1386
|
-
requestedModel: argv.model,
|
|
1383
|
+
requestedModel: argv.originalModel || argv.model,
|
|
1387
1384
|
tool: argv.tool || 'claude',
|
|
1388
1385
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
1389
1386
|
resultModelUsage,
|
|
@@ -88,7 +88,7 @@ export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
|
88
88
|
sanitizeLogContent,
|
|
89
89
|
verbose: argv.verbose,
|
|
90
90
|
errorMessage: `The solver stopped before creating a pull request.\n\nReason: ${reason || 'Unknown error'}`,
|
|
91
|
-
requestedModel: argv.model,
|
|
91
|
+
requestedModel: argv.originalModel || argv.model,
|
|
92
92
|
tool: argv.tool || 'claude',
|
|
93
93
|
});
|
|
94
94
|
if (uploaded) {
|
|
@@ -47,12 +47,13 @@ export const { buildClaudeResumeCommand, buildClaudeInitialCommand } = claudeCom
|
|
|
47
47
|
* @param {string} options.sessionId - The session ID to resume
|
|
48
48
|
* @param {string|null} [options.tool] - Tool name (codex, opencode, agent)
|
|
49
49
|
* @param {string|null} [options.model] - Model name to preserve
|
|
50
|
+
* @param {string|null} [options.fallbackModel] - Explicit fallback model to preserve
|
|
50
51
|
* @param {string|null} [options.tempDir] - Working directory to preserve
|
|
51
52
|
* @param {string} [options.nodePath] - Node binary path
|
|
52
53
|
* @param {string} [options.scriptPath] - solve.mjs path
|
|
53
54
|
* @returns {string}
|
|
54
55
|
*/
|
|
55
|
-
export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, model = null, tempDir = null, nodePath = process.argv[0], scriptPath = process.argv[1] }) => {
|
|
56
|
+
export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, model = null, fallbackModel = null, tempDir = null, nodePath = process.argv[0], scriptPath = process.argv[1] }) => {
|
|
56
57
|
const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
57
58
|
|
|
58
59
|
const args = [shellQuote(scriptPath), shellQuote(issueUrl), '--resume', shellQuote(sessionId)];
|
|
@@ -65,6 +66,10 @@ export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, mode
|
|
|
65
66
|
args.push('--model', shellQuote(model));
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
if (fallbackModel) {
|
|
70
|
+
args.push('--fallback-model', shellQuote(fallbackModel));
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
if (tempDir) {
|
|
69
74
|
args.push('--working-directory', shellQuote(tempDir));
|
|
70
75
|
}
|
|
@@ -566,7 +571,7 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
566
571
|
await log(` ${claudeResumeCmd}`);
|
|
567
572
|
await log('');
|
|
568
573
|
} else if (issueUrl) {
|
|
569
|
-
const solveResumeCmd = buildSolveResumeCommand({ issueUrl, sessionId, tool, model: argv.model, tempDir });
|
|
574
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
|
|
570
575
|
await log('');
|
|
571
576
|
await log(`š” To continue this ${tool} session with solve:`);
|
|
572
577
|
await log('');
|
|
@@ -577,11 +582,12 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
577
582
|
if (limitReached) {
|
|
578
583
|
await log('ā° LIMIT REACHED DETECTED!');
|
|
579
584
|
|
|
580
|
-
if (argv.autoResumeOnLimitReset && global.limitResetTime) {
|
|
581
|
-
|
|
585
|
+
if ((argv.autoResumeOnLimitReset || argv.autoRestartOnLimitReset) && global.limitResetTime) {
|
|
586
|
+
const isRestart = !!argv.autoRestartOnLimitReset;
|
|
587
|
+
await log(`\nš AUTO-${isRestart ? 'RESTART' : 'RESUME'} ON LIMIT RESET ENABLED - Will ${isRestart ? 'restart' : 'resume'} at ${global.limitResetTime}`);
|
|
582
588
|
// Pass tempDir to ensure resumed session uses the same working directory
|
|
583
589
|
// This is critical for Claude Code session resume to work correctly
|
|
584
|
-
await autoContinueWhenLimitResets(issueUrl, sessionId, argv, shouldAttachLogs, tempDir);
|
|
590
|
+
await autoContinueWhenLimitResets(issueUrl, sessionId, argv, shouldAttachLogs, tempDir, isRestart);
|
|
585
591
|
} else {
|
|
586
592
|
if (global.limitResetTime) {
|
|
587
593
|
await log(`\nā° Limit resets at: ${global.limitResetTime}`);
|
|
@@ -823,7 +829,7 @@ Fixes ${issueRef}
|
|
|
823
829
|
// Issue #1152: Pass sessionType for differentiated log comments
|
|
824
830
|
sessionType,
|
|
825
831
|
// Issue #1225: Pass model and tool info for PR comments
|
|
826
|
-
requestedModel: argv.model,
|
|
832
|
+
requestedModel: argv.originalModel || argv.model,
|
|
827
833
|
tool: argv.tool || 'claude',
|
|
828
834
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
829
835
|
resultModelUsage,
|
|
@@ -909,7 +915,7 @@ Fixes ${issueRef}
|
|
|
909
915
|
// Issue #1152: Pass sessionType for differentiated log comments
|
|
910
916
|
sessionType,
|
|
911
917
|
// Issue #1225: Pass model and tool info for issue comments
|
|
912
|
-
requestedModel: argv.model,
|
|
918
|
+
requestedModel: argv.originalModel || argv.model,
|
|
913
919
|
tool: argv.tool || 'claude',
|
|
914
920
|
// Issue #1454: Pass resultModelUsage for accurate multi-model display
|
|
915
921
|
resultModelUsage,
|
|
@@ -1000,7 +1006,7 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
1000
1006
|
verbose: argv.verbose || false,
|
|
1001
1007
|
errorMessage: cleanErrorMessage(error),
|
|
1002
1008
|
// Issue #1225: Pass model and tool info for PR comments
|
|
1003
|
-
requestedModel: argv.model,
|
|
1009
|
+
requestedModel: argv.originalModel || argv.model,
|
|
1004
1010
|
tool: argv.tool || 'claude',
|
|
1005
1011
|
});
|
|
1006
1012
|
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -39,6 +39,7 @@ const { checkPRMerged, checkForUncommittedChanges, getUncommittedChangesDetails,
|
|
|
39
39
|
|
|
40
40
|
// Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
|
|
41
41
|
const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
|
|
42
|
+
const { formatAutoIterationLimit, hasReachedAutoIterationLimit, normalizeAutoIterationLimit } = await import('./auto-iteration-limits.lib.mjs');
|
|
42
43
|
|
|
43
44
|
// Issue #1625: Central marker constants + tracked comment posting
|
|
44
45
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
@@ -52,7 +53,7 @@ export const watchForFeedback = async params => {
|
|
|
52
53
|
|
|
53
54
|
const watchInterval = argv.watchInterval || 60; // seconds
|
|
54
55
|
const isTemporaryWatch = argv.temporaryWatch || false;
|
|
55
|
-
const maxAutoRestartIterations = argv.autoRestartMaxIterations
|
|
56
|
+
const maxAutoRestartIterations = normalizeAutoIterationLimit(argv.autoRestartMaxIterations);
|
|
56
57
|
|
|
57
58
|
// Track latest session data across all iterations for accurate pricing
|
|
58
59
|
let latestSessionId = null;
|
|
@@ -75,7 +76,7 @@ export const watchForFeedback = async params => {
|
|
|
75
76
|
await log(formatAligned('', 'Monitoring PR:', `#${prNumber}`, 2));
|
|
76
77
|
await log(formatAligned('', 'Mode:', 'Auto-restart (NOT --watch mode)', 2));
|
|
77
78
|
await log(formatAligned('', 'Stop conditions:', 'All changes committed OR PR merged OR max iterations reached', 2));
|
|
78
|
-
await log(formatAligned('', 'Max iterations:',
|
|
79
|
+
await log(formatAligned('', 'Max iterations:', formatAutoIterationLimit(maxAutoRestartIterations), 2));
|
|
79
80
|
await log(formatAligned('', 'Note:', 'No wait time between iterations in auto-restart mode', 2));
|
|
80
81
|
} else {
|
|
81
82
|
await log(formatAligned('šļø', 'WATCH MODE ACTIVATED', ''));
|
|
@@ -117,7 +118,7 @@ export const watchForFeedback = async params => {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// Check if we've reached max iterations
|
|
120
|
-
if (autoRestartCount
|
|
121
|
+
if (hasReachedAutoIterationLimit(autoRestartCount, maxAutoRestartIterations)) {
|
|
121
122
|
await log('');
|
|
122
123
|
await log(formatAligned('ā ļø', 'MAX ITERATIONS REACHED', `Exiting auto-restart mode after ${autoRestartCount} iterations`));
|
|
123
124
|
await log(formatAligned('', 'Some uncommitted changes may remain', '', 2));
|
|
@@ -188,7 +189,7 @@ export const watchForFeedback = async params => {
|
|
|
188
189
|
// Post a comment to PR about auto-restart
|
|
189
190
|
if (prNumber) {
|
|
190
191
|
try {
|
|
191
|
-
const remainingIterations = maxAutoRestartIterations - autoRestartCount;
|
|
192
|
+
const remainingIterations = maxAutoRestartIterations === 0 ? null : maxAutoRestartIterations - autoRestartCount;
|
|
192
193
|
|
|
193
194
|
// Get uncommitted files list for the comment
|
|
194
195
|
let uncommittedFilesList = '';
|
|
@@ -196,7 +197,9 @@ export const watchForFeedback = async params => {
|
|
|
196
197
|
uncommittedFilesList = '\n\n**Uncommitted files:**\n```\n' + changes.join('\n') + '\n```';
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
const
|
|
200
|
+
const iterationLabel = maxAutoRestartIterations === 0 ? `${autoRestartCount}` : `${autoRestartCount}/${maxAutoRestartIterations}`;
|
|
201
|
+
const stopText = remainingIterations === null ? 'Auto-restart is configured with no iteration limit.' : `Auto-restart will stop after changes are committed or discarded, or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}.`;
|
|
202
|
+
const commentBody = `## š ${AUTO_RESTART_MARKER} ${iterationLabel}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit or discard them.${uncommittedFilesList}\n\n---\n*${stopText} Please wait until working session will end and give your feedback.*`;
|
|
200
203
|
// Issue #1625: Track so this doesn't falsely count as AI-authored.
|
|
201
204
|
await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: commentBody });
|
|
202
205
|
await log(formatAligned('', 'š¬ Posted auto-restart notification to PR', '', 2));
|
|
@@ -283,7 +286,8 @@ export const watchForFeedback = async params => {
|
|
|
283
286
|
const logFile = getLogFile();
|
|
284
287
|
if (logFile) {
|
|
285
288
|
// Use "Auto-restart X/Y Failure Log" format to distinguish from success logs
|
|
286
|
-
const
|
|
289
|
+
const iterationLabel = maxAutoRestartIterations === 0 ? `${autoRestartCount}` : `${autoRestartCount}/${maxAutoRestartIterations}`;
|
|
290
|
+
const customTitle = `ā ļø Auto-restart ${iterationLabel} Failure Log`;
|
|
287
291
|
const logUploadSuccess = await attachLogToGitHub({
|
|
288
292
|
logFile,
|
|
289
293
|
targetType: 'pr',
|
|
@@ -306,7 +310,7 @@ export const watchForFeedback = async params => {
|
|
|
306
310
|
isUsageLimit: toolResult.limitReached,
|
|
307
311
|
limitResetTime: toolResult.limitResetTime,
|
|
308
312
|
// Issue #1225: Pass model and tool info for PR comments
|
|
309
|
-
requestedModel: argv.model,
|
|
313
|
+
requestedModel: argv.originalModel || argv.model,
|
|
310
314
|
tool: argv.tool || 'claude',
|
|
311
315
|
// Issue #1508: Pass model usage for failure log (cost info per model)
|
|
312
316
|
resultModelUsage: toolResult.resultModelUsage || null,
|
|
@@ -372,7 +376,8 @@ export const watchForFeedback = async params => {
|
|
|
372
376
|
const logFile = getLogFile();
|
|
373
377
|
if (logFile) {
|
|
374
378
|
// Use "Auto-restart X/Y Log" format as requested in issue #1107
|
|
375
|
-
const
|
|
379
|
+
const iterationLabel = maxAutoRestartIterations === 0 ? `${autoRestartCount}` : `${autoRestartCount}/${maxAutoRestartIterations}`;
|
|
380
|
+
const customTitle = `š Auto-restart ${iterationLabel} Log`;
|
|
376
381
|
const logUploadSuccess = await attachLogToGitHub({
|
|
377
382
|
logFile,
|
|
378
383
|
targetType: 'pr',
|
|
@@ -391,7 +396,7 @@ export const watchForFeedback = async params => {
|
|
|
391
396
|
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
392
397
|
pricingInfo: toolResult.pricingInfo,
|
|
393
398
|
// Issue #1225: Pass model and tool info for PR comments
|
|
394
|
-
requestedModel: argv.model,
|
|
399
|
+
requestedModel: argv.originalModel || argv.model,
|
|
395
400
|
tool: argv.tool || 'claude',
|
|
396
401
|
// Issue #1508: Include budget stats (context/token/cost) for auto-restart log
|
|
397
402
|
resultModelUsage: toolResult.resultModelUsage || null,
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -49,7 +49,7 @@ const { applySolveToolAlias, getFirstParsedPositionalArg, getSolveCommandNameFro
|
|
|
49
49
|
const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
|
|
50
50
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
51
51
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
52
|
-
const { trackSession, startSessionMonitoring,
|
|
52
|
+
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
53
53
|
|
|
54
54
|
const config = yargs(hideBin(process.argv))
|
|
55
55
|
.usage('Usage: hive-telegram-bot [options]')
|
|
@@ -549,7 +549,7 @@ async function safeReply(ctx, text, options = {}) {
|
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock, perCommandIsolation = null) {
|
|
552
|
+
async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock, perCommandIsolation = null, tool = 'claude') {
|
|
553
553
|
const { chat, message_id: msgId } = startingMessage;
|
|
554
554
|
const safeEdit = async text => {
|
|
555
555
|
try {
|
|
@@ -567,19 +567,19 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
567
567
|
VERBOSE && console.log(`[VERBOSE] Using isolation (${iso.backend}), session: ${session}`);
|
|
568
568
|
result = await iso.runner.executeWithIsolation(commandName, args, { backend: iso.backend, sessionId: session, verbose: VERBOSE });
|
|
569
569
|
extraInfo = `\nš Isolation: \`${iso.backend}\``;
|
|
570
|
-
if (result.success) trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, isolationBackend: iso.backend, sessionId: session }, VERBOSE);
|
|
570
|
+
if (result.success) trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, isolationBackend: iso.backend, sessionId: session, tool }, VERBOSE);
|
|
571
571
|
} else {
|
|
572
572
|
result = await executeStartScreen(commandName, args);
|
|
573
573
|
const match = result.success && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
|
|
574
574
|
session = match ? match[1] : 'unknown';
|
|
575
575
|
// Issue #1586: Track non-isolation sessions with timeout-based expiry.
|
|
576
576
|
// These sessions cannot reliably detect completion (screen stays alive via
|
|
577
|
-
// `exec bash`), so
|
|
577
|
+
// `exec bash`), so active URL checks auto-expire them after 10 min.
|
|
578
578
|
// This prevents accidental duplicate commands within the timeout window.
|
|
579
|
-
if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName }, VERBOSE);
|
|
579
|
+
if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool }, VERBOSE);
|
|
580
580
|
}
|
|
581
581
|
if (result.warning) return safeEdit(`ā ļø ${result.warning}`);
|
|
582
|
-
if (result.success) await safeEdit(
|
|
582
|
+
if (result.success) await safeEdit(`š ${commandName.charAt(0).toUpperCase() + commandName.slice(1)} command executing...\n\nStatus: \`Executing...\`\nš Session: \`${session}\`${extraInfo}\n\n${infoBlock}\n\nš This message will update when the session finishes.`);
|
|
583
583
|
else await safeEdit(`ā Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
|
|
584
584
|
}
|
|
585
585
|
|
|
@@ -990,7 +990,7 @@ async function handleSolveCommand(ctx) {
|
|
|
990
990
|
return;
|
|
991
991
|
}
|
|
992
992
|
// Issue #1567: Prevent concurrent sessions on the same PR/issue
|
|
993
|
-
const activeSession =
|
|
993
|
+
const activeSession = await hasActiveSessionForUrlAsync(normalizedUrl, VERBOSE);
|
|
994
994
|
if (activeSession.isActive) {
|
|
995
995
|
await safeReply(ctx, `ā A working session is already running for this URL.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nSession: \`${activeSession.sessionName}\`\n\nš” Wait for the current session to complete, or use /solve\\_stop to cancel it.`, { reply_to_message_id: ctx.message.message_id });
|
|
996
996
|
return;
|
|
@@ -1006,7 +1006,7 @@ async function handleSolveCommand(ctx) {
|
|
|
1006
1006
|
const toolQueuedCount = queueStats.queuedByTool[solveTool] || 0; // tool-specific queue count (#1551)
|
|
1007
1007
|
if (check.canStart && toolQueuedCount === 0) {
|
|
1008
1008
|
const startingMessage = await safeReply(ctx, `š Starting solve command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
|
|
1009
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock, effectiveSolveIsolation);
|
|
1009
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock, effectiveSolveIsolation, solveTool);
|
|
1010
1010
|
} else {
|
|
1011
1011
|
const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation });
|
|
1012
1012
|
let queueMessage = `š Solve command queued (${solveTool} queue position #${toolQueuedCount + 1})\n\n${infoBlock}`; // tool-specific position (#1551)
|
|
@@ -1171,7 +1171,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1171
1171
|
}
|
|
1172
1172
|
|
|
1173
1173
|
const startingMessage = await safeReply(ctx, `š Starting hive command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
|
|
1174
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock, effectiveHiveIsolation);
|
|
1174
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock, effectiveHiveIsolation, hiveTool);
|
|
1175
1175
|
}
|
|
1176
1176
|
|
|
1177
1177
|
bot.command(/^hive$/i, handleHiveCommand);
|
|
@@ -80,8 +80,8 @@ export function createIsolationAwareQueueCallback(botIsolationBackend, botIsolat
|
|
|
80
80
|
if (iso) {
|
|
81
81
|
const sid = iso.runner.generateSessionId();
|
|
82
82
|
const r = await iso.runner.executeWithIsolation(item.command || 'solve', item.args, { backend: iso.backend, sessionId: sid, verbose });
|
|
83
|
-
if (r.success) trackSession(sid, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: item.command || 'solve', isolationBackend: iso.backend, sessionId: sid }, verbose);
|
|
84
|
-
return { ...r, output: r.output || `session: ${sid}` };
|
|
83
|
+
if (r.success) trackSession(sid, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: item.command || 'solve', isolationBackend: iso.backend, sessionId: sid, tool: item.tool || 'claude' }, verbose);
|
|
84
|
+
return { ...r, sessionId: sid, isolationBackend: iso.backend, output: r.output || `session: ${sid}` };
|
|
85
85
|
}
|
|
86
86
|
return fallbackCallback(item);
|
|
87
87
|
};
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { getCachedClaudeLimits, getCachedCodexLimits, getCachedGitHubLimits, getCachedMemoryInfo, getCachedCpuInfo, getCachedDiskInfo, getLimitCache } from './limits.lib.mjs';
|
|
19
19
|
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
20
|
-
import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses,
|
|
20
|
+
import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
21
21
|
export { QUEUE_CONFIG, THRESHOLD_STRATEGIES } from './queue-config.lib.mjs';
|
|
22
22
|
import { QUEUE_CONFIG } from './queue-config.lib.mjs';
|
|
23
23
|
|
|
@@ -133,6 +133,8 @@ export class SolveQueue {
|
|
|
133
133
|
this.verbose = options.verbose || false;
|
|
134
134
|
this.executeCallback = options.executeCallback || null;
|
|
135
135
|
this.messageUpdateCallback = options.messageUpdateCallback || null;
|
|
136
|
+
this.getRunningProcessesFn = options.getRunningProcesses || getRunningProcesses;
|
|
137
|
+
this.getRunningIsolatedSessionsFn = options.getRunningIsolatedSessions || getRunningIsolatedSessions;
|
|
136
138
|
|
|
137
139
|
// Separate queues per tool type - claude tasks never block agent tasks
|
|
138
140
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
@@ -462,6 +464,46 @@ export class SolveQueue {
|
|
|
462
464
|
};
|
|
463
465
|
}
|
|
464
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Get external processing counts from both process scanning and tracked
|
|
469
|
+
* isolated sessions. The displayed/accounted value is the maximum of the two
|
|
470
|
+
* sources so screen-isolated sessions remain visible even when the AI CLI
|
|
471
|
+
* process is not directly observable, while regular non-isolated runs still
|
|
472
|
+
* use pgrep as before.
|
|
473
|
+
*
|
|
474
|
+
* @param {string[]} tools - Tool queues to count
|
|
475
|
+
* @returns {Promise<{byTool: Object, processByTool: Object, isolatedByTool: Object, total: number, isolatedTotal: number, processTotal: number}>}
|
|
476
|
+
*/
|
|
477
|
+
async getExternalProcessingSnapshot(tools = Object.keys(this.queues)) {
|
|
478
|
+
const uniqueTools = [...new Set(tools)];
|
|
479
|
+
const isolated = await this.getRunningIsolatedSessionsFn(this.verbose);
|
|
480
|
+
const isolatedByTool = isolated.byTool || {};
|
|
481
|
+
const processByTool = {};
|
|
482
|
+
const byTool = {};
|
|
483
|
+
|
|
484
|
+
await Promise.all(
|
|
485
|
+
uniqueTools.map(async tool => {
|
|
486
|
+
const result = await this.getRunningProcessesFn(tool, this.verbose);
|
|
487
|
+
const processCount = result?.count || 0;
|
|
488
|
+
const isolatedCount = isolatedByTool[tool] || 0;
|
|
489
|
+
processByTool[tool] = processCount;
|
|
490
|
+
byTool[tool] = Math.max(processCount, isolatedCount);
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const processTotal = Object.values(processByTool).reduce((sum, count) => sum + count, 0);
|
|
495
|
+
const isolatedTotal = isolated.count || Object.values(isolatedByTool).reduce((sum, count) => sum + count, 0);
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
byTool,
|
|
499
|
+
processByTool,
|
|
500
|
+
isolatedByTool,
|
|
501
|
+
total: Math.max(processTotal, isolatedTotal),
|
|
502
|
+
isolatedTotal,
|
|
503
|
+
processTotal,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
465
507
|
/**
|
|
466
508
|
* Check if a new command can start
|
|
467
509
|
*
|
|
@@ -505,16 +547,19 @@ export class SolveQueue {
|
|
|
505
547
|
}
|
|
506
548
|
}
|
|
507
549
|
|
|
508
|
-
// Check running
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
const
|
|
550
|
+
// Check running tool processes (this is a metric, not a blocking reason by itself).
|
|
551
|
+
// For screen-isolated sessions, use the maximum of `$ --status` executing
|
|
552
|
+
// counts and pgrep counts so detached sessions remain visible.
|
|
553
|
+
const externalProcessing = await this.getExternalProcessingSnapshot([...Object.keys(this.queues), tool]);
|
|
554
|
+
const claudeProcessCount = externalProcessing.byTool.claude || 0;
|
|
555
|
+
const codexProcessCount = externalProcessing.byTool.codex || 0;
|
|
556
|
+
const agentProcessCount = externalProcessing.byTool.agent || 0;
|
|
557
|
+
const hasRunningClaude = claudeProcessCount > 0;
|
|
558
|
+
const hasRunningCodex = codexProcessCount > 0;
|
|
514
559
|
|
|
515
560
|
// Calculate total processing count for system resources (all tools)
|
|
516
561
|
// System resources (RAM, CPU, disk) apply to all tools
|
|
517
|
-
const totalProcessing = this.processing.size +
|
|
562
|
+
const totalProcessing = this.processing.size + externalProcessing.total;
|
|
518
563
|
|
|
519
564
|
// Calculate Claude-specific processing count for Claude API limits
|
|
520
565
|
// Only counts Claude items in queue + external claude processes
|
|
@@ -572,10 +617,10 @@ export class SolveQueue {
|
|
|
572
617
|
// Add claude_running info at the END (not beginning) of reasons
|
|
573
618
|
// Since it's supplementary info, not the primary blocking reason
|
|
574
619
|
// See: https://github.com/link-assistant/hive-mind/issues/1078
|
|
575
|
-
reasons.push(formatWaitingReason('claude_running',
|
|
620
|
+
reasons.push(formatWaitingReason('claude_running', claudeProcessCount, 0) + ` (${claudeProcessCount} processes)`);
|
|
576
621
|
}
|
|
577
622
|
if (tool === 'codex' && hasRunningCodex && reasons.length > 0) {
|
|
578
|
-
reasons.push(formatWaitingReason('codex_running',
|
|
623
|
+
reasons.push(formatWaitingReason('codex_running', codexProcessCount, 0) + ` (${codexProcessCount} processes)`);
|
|
579
624
|
}
|
|
580
625
|
|
|
581
626
|
const canStart = reasons.length === 0 && !rejected;
|
|
@@ -595,8 +640,10 @@ export class SolveQueue {
|
|
|
595
640
|
reason: reasons.length > 0 ? reasons.join('\n') : undefined,
|
|
596
641
|
reasons,
|
|
597
642
|
oneAtATime,
|
|
598
|
-
claudeProcesses:
|
|
599
|
-
codexProcesses:
|
|
643
|
+
claudeProcesses: claudeProcessCount,
|
|
644
|
+
codexProcesses: codexProcessCount,
|
|
645
|
+
agentProcesses: agentProcessCount,
|
|
646
|
+
isolatedProcesses: externalProcessing.isolatedTotal,
|
|
600
647
|
totalProcessing,
|
|
601
648
|
claudeProcessingCount,
|
|
602
649
|
codexProcessingCount,
|
|
@@ -1075,7 +1122,7 @@ export class SolveQueue {
|
|
|
1075
1122
|
const result = await this.executeCallback(item);
|
|
1076
1123
|
|
|
1077
1124
|
// Extract session name from result
|
|
1078
|
-
let sessionName = 'unknown';
|
|
1125
|
+
let sessionName = result?.sessionId || 'unknown';
|
|
1079
1126
|
if (result && result.output) {
|
|
1080
1127
|
const sessionMatch = result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/);
|
|
1081
1128
|
if (sessionMatch) sessionName = sessionMatch[1];
|
|
@@ -1098,7 +1145,8 @@ export class SolveQueue {
|
|
|
1098
1145
|
if (result.warning) {
|
|
1099
1146
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `ā ļø ${result.warning}`, { parse_mode: 'Markdown' });
|
|
1100
1147
|
} else if (result.success) {
|
|
1101
|
-
const
|
|
1148
|
+
const isolationInfo = result.isolationBackend ? `\nš Isolation: \`${result.isolationBackend}\`` : '';
|
|
1149
|
+
const response = `š Solve command executing...\n\nStatus: \`Executing...\`\nš Session: \`${sessionName}\`${isolationInfo}\n\n${item.infoBlock}\n\nš This message will update when the session finishes.`;
|
|
1102
1150
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
|
|
1103
1151
|
} else {
|
|
1104
1152
|
const response = `ā Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``;
|
|
@@ -1177,9 +1225,8 @@ export class SolveQueue {
|
|
|
1177
1225
|
* Format queue status for display in /limits command
|
|
1178
1226
|
* Shows per-tool queue breakdown with processing counts.
|
|
1179
1227
|
*
|
|
1180
|
-
* Processing count = actual
|
|
1181
|
-
*
|
|
1182
|
-
* work happens in the spawned system process (claude, agent, etc.).
|
|
1228
|
+
* Processing count = max(actual AI CLI processes via pgrep, tracked
|
|
1229
|
+
* `$ --status` executing screen-isolated sessions), not queue state.
|
|
1183
1230
|
*
|
|
1184
1231
|
* Output format:
|
|
1185
1232
|
* ```
|
|
@@ -1194,10 +1241,11 @@ export class SolveQueue {
|
|
|
1194
1241
|
*/
|
|
1195
1242
|
async formatStatus() {
|
|
1196
1243
|
// Always show per-tool breakdown for all known queues
|
|
1244
|
+
const externalProcessing = await this.getExternalProcessingSnapshot(Object.keys(this.queues));
|
|
1197
1245
|
let message = 'Queues\n';
|
|
1198
1246
|
for (const [tool, toolQueue] of Object.entries(this.queues)) {
|
|
1199
1247
|
const pending = toolQueue.length;
|
|
1200
|
-
const processing =
|
|
1248
|
+
const processing = externalProcessing.byTool[tool] || 0;
|
|
1201
1249
|
message += `${tool} (pending: ${pending}, processing: ${processing})\n`;
|
|
1202
1250
|
}
|
|
1203
1251
|
|
|
@@ -1208,9 +1256,8 @@ export class SolveQueue {
|
|
|
1208
1256
|
* Format detailed queue status for Telegram message
|
|
1209
1257
|
* Groups output by tool queue, shows first 5 items per queue, and uses human-readable time.
|
|
1210
1258
|
*
|
|
1211
|
-
* Processing count = actual
|
|
1212
|
-
*
|
|
1213
|
-
* work happens in the spawned system process (claude, agent, etc.).
|
|
1259
|
+
* Processing count = max(actual AI CLI processes via pgrep, tracked
|
|
1260
|
+
* `$ --status` executing screen-isolated sessions), not queue state.
|
|
1214
1261
|
*
|
|
1215
1262
|
* Output format:
|
|
1216
1263
|
* ```
|
|
@@ -1231,16 +1278,17 @@ export class SolveQueue {
|
|
|
1231
1278
|
*/
|
|
1232
1279
|
async formatDetailedStatus() {
|
|
1233
1280
|
const stats = this.getStats();
|
|
1281
|
+
const externalProcessing = await this.getExternalProcessingSnapshot(Object.keys(this.queues));
|
|
1234
1282
|
|
|
1235
|
-
// Get actual
|
|
1236
|
-
//
|
|
1237
|
-
//
|
|
1283
|
+
// Get actual processing counts for each tool queue.
|
|
1284
|
+
// This combines pgrep with tracked isolation status so users see detached
|
|
1285
|
+
// screen-isolated work even when the direct AI CLI process count is lower.
|
|
1238
1286
|
let message = 'š *Solve Queue Status*\n\n';
|
|
1239
1287
|
|
|
1240
1288
|
// Show per-tool queue breakdown with items grouped by queue
|
|
1241
1289
|
for (const [tool, toolQueue] of Object.entries(this.queues)) {
|
|
1242
1290
|
const pending = toolQueue.length;
|
|
1243
|
-
const processing =
|
|
1291
|
+
const processing = externalProcessing.byTool[tool] || 0;
|
|
1244
1292
|
message += `*${tool}* (pending: ${pending}, processing: ${processing})\n`;
|
|
1245
1293
|
|
|
1246
1294
|
// Show first 5 queued items for this tool
|
|
@@ -1308,7 +1356,7 @@ export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
|
|
|
1308
1356
|
const match = result.output && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
|
|
1309
1357
|
const session = match ? match[1] : null;
|
|
1310
1358
|
if (session) {
|
|
1311
|
-
trackSessionFn(session, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve' });
|
|
1359
|
+
trackSessionFn(session, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve', tool: item.tool || 'claude' });
|
|
1312
1360
|
}
|
|
1313
1361
|
}
|
|
1314
1362
|
return result;
|
|
@@ -1316,23 +1364,21 @@ export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
|
|
|
1316
1364
|
}
|
|
1317
1365
|
|
|
1318
1366
|
/**
|
|
1319
|
-
* Get count of
|
|
1320
|
-
*
|
|
1321
|
-
* for more reliable task counting.
|
|
1367
|
+
* Get count of tracked isolated sessions that are still executing according
|
|
1368
|
+
* to `$ --status`. Queue display combines this with pgrep counts using max().
|
|
1322
1369
|
*
|
|
1323
1370
|
* @param {boolean} verbose - Whether to log verbose output
|
|
1324
|
-
* @returns {Promise<{count: number, sessions: string[]}>}
|
|
1371
|
+
* @returns {Promise<{count: number, sessions: string[], byTool: Object}>}
|
|
1325
1372
|
*/
|
|
1326
1373
|
export async function getRunningIsolatedSessions(verbose = false) {
|
|
1327
1374
|
try {
|
|
1328
|
-
const {
|
|
1329
|
-
|
|
1330
|
-
return { count, sessions: [] };
|
|
1375
|
+
const { getRunningTrackedIsolationSessions } = await import('./session-monitor.lib.mjs');
|
|
1376
|
+
return await getRunningTrackedIsolationSessions(verbose);
|
|
1331
1377
|
} catch (error) {
|
|
1332
1378
|
if (verbose) {
|
|
1333
1379
|
console.error(`[VERBOSE] /solve_queue error getting isolated sessions:`, error.message);
|
|
1334
1380
|
}
|
|
1335
|
-
return { count: 0, sessions: [] };
|
|
1381
|
+
return { count: 0, sessions: [], byTool: {} };
|
|
1336
1382
|
}
|
|
1337
1383
|
}
|
|
1338
1384
|
|