@link-assistant/hive-mind 1.56.8 → 1.56.9
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.56.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 94448c3: Fix screen-isolated work-session Telegram updates so executing messages stay compact and completion messages use `$ --status` start/end timestamps and exit codes.
|
|
8
|
+
|
|
3
9
|
## 1.56.8
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import { promisify } from 'util';
|
|
19
19
|
import { exec as execCallback } from 'child_process';
|
|
20
|
+
import { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
|
|
21
|
+
|
|
22
|
+
export { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
|
|
20
23
|
|
|
21
24
|
const exec = promisify(execCallback);
|
|
22
25
|
|
|
@@ -201,6 +204,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
201
204
|
for (const { sessionName, sessionInfo } of sessions) {
|
|
202
205
|
let stillRunning;
|
|
203
206
|
let exitCode = null;
|
|
207
|
+
let statusResult = null;
|
|
204
208
|
|
|
205
209
|
if (sessionInfo.isolationBackend && sessionInfo.sessionId) {
|
|
206
210
|
// Isolation mode: use $ --status, with screen -ls only as a fallback
|
|
@@ -209,6 +213,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
209
213
|
const state = await getIsolationSessionState(sessionName, sessionInfo, { verbose });
|
|
210
214
|
stillRunning = state.running;
|
|
211
215
|
exitCode = state.exitCode;
|
|
216
|
+
statusResult = state.statusResult;
|
|
212
217
|
} else {
|
|
213
218
|
// Issue #1586: Non-isolation screen sessions cannot reliably detect
|
|
214
219
|
// completion because start-screen keeps the screen alive via `exec bash`.
|
|
@@ -233,21 +238,14 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
233
238
|
console.log(`Session ${sessionName} has finished. Sending notification to chat ${sessionInfo.chatId}`);
|
|
234
239
|
|
|
235
240
|
try {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const isolationInfo = sessionInfo.isolationBackend ? `\n🔒 Isolation: ${sessionInfo.isolationBackend}` : '';
|
|
245
|
-
|
|
246
|
-
let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
|
|
247
|
-
message += `📊 Session: \`${sessionName}\`\n`;
|
|
248
|
-
message += `⏱️ Duration: ${minutes}m ${seconds}s\n`;
|
|
249
|
-
message += `🔗 URL: ${sessionInfo.url}${isolationInfo}\n\n`;
|
|
250
|
-
message += `The work session has finished. You can now review the results.`;
|
|
241
|
+
const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
|
|
242
|
+
const message = formatSessionCompletionMessage({
|
|
243
|
+
sessionName,
|
|
244
|
+
sessionInfo,
|
|
245
|
+
statusResult,
|
|
246
|
+
observedEndTime: new Date(),
|
|
247
|
+
exitCode: finalExitCode,
|
|
248
|
+
});
|
|
251
249
|
|
|
252
250
|
// Update the original reply message if messageId is available, otherwise send new message
|
|
253
251
|
if (sessionInfo.messageId) {
|
|
@@ -256,7 +254,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
256
254
|
await bot.telegram.sendMessage(sessionInfo.chatId, message, { parse_mode: 'Markdown' });
|
|
257
255
|
}
|
|
258
256
|
|
|
259
|
-
completeSession(sessionName,
|
|
257
|
+
completeSession(sessionName, finalExitCode || 0, verbose);
|
|
260
258
|
} catch (error) {
|
|
261
259
|
console.error(`Failed to send completion notification for ${sessionName}:`, error);
|
|
262
260
|
completeSession(sessionName, 1, verbose);
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -50,6 +50,7 @@ const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STO
|
|
|
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
52
|
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
53
|
+
const { formatExecutingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
|
|
53
54
|
|
|
54
55
|
const config = yargs(hideBin(process.argv))
|
|
55
56
|
.usage('Usage: hive-telegram-bot [options]')
|
|
@@ -559,14 +560,11 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
559
560
|
}
|
|
560
561
|
};
|
|
561
562
|
const iso = await resolveIsolation(perCommandIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE);
|
|
562
|
-
let result,
|
|
563
|
-
session,
|
|
564
|
-
extraInfo = '';
|
|
563
|
+
let result, session;
|
|
565
564
|
if (iso) {
|
|
566
565
|
session = iso.runner.generateSessionId();
|
|
567
566
|
VERBOSE && console.log(`[VERBOSE] Using isolation (${iso.backend}), session: ${session}`);
|
|
568
567
|
result = await iso.runner.executeWithIsolation(commandName, args, { backend: iso.backend, sessionId: session, verbose: VERBOSE });
|
|
569
|
-
extraInfo = `\n🔒 Isolation: \`${iso.backend}\``;
|
|
570
568
|
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
569
|
} else {
|
|
572
570
|
result = await executeStartScreen(commandName, args);
|
|
@@ -579,8 +577,16 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
579
577
|
if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool }, VERBOSE);
|
|
580
578
|
}
|
|
581
579
|
if (result.warning) return safeEdit(`⚠️ ${result.warning}`);
|
|
582
|
-
if (result.success)
|
|
583
|
-
|
|
580
|
+
if (result.success) {
|
|
581
|
+
await safeEdit(
|
|
582
|
+
formatExecutingWorkSessionMessage({
|
|
583
|
+
commandName,
|
|
584
|
+
sessionName: session,
|
|
585
|
+
isolationBackend: iso?.backend || null,
|
|
586
|
+
infoBlock,
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
} else await safeEdit(`❌ Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
|
|
584
590
|
}
|
|
585
591
|
|
|
586
592
|
bot.command('help', async ctx => {
|
|
@@ -20,6 +20,7 @@ export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, ge
|
|
|
20
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
|
+
import { formatExecutingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
23
24
|
|
|
24
25
|
export const QueueItemStatus = {
|
|
25
26
|
QUEUED: 'queued',
|
|
@@ -1145,8 +1146,12 @@ export class SolveQueue {
|
|
|
1145
1146
|
if (result.warning) {
|
|
1146
1147
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `⚠️ ${result.warning}`, { parse_mode: 'Markdown' });
|
|
1147
1148
|
} else if (result.success) {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1149
|
+
const response = formatExecutingWorkSessionMessage({
|
|
1150
|
+
commandName: item.command || 'solve',
|
|
1151
|
+
sessionName,
|
|
1152
|
+
isolationBackend: result.isolationBackend,
|
|
1153
|
+
infoBlock: item.infoBlock,
|
|
1154
|
+
});
|
|
1150
1155
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
|
|
1151
1156
|
} else {
|
|
1152
1157
|
const response = `❌ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const FAILURE_STATUSES = new Set(['failed', 'cancelled', 'canceled', 'error']);
|
|
2
|
+
|
|
3
|
+
function capitalizeCommandName(commandName) {
|
|
4
|
+
const normalized = commandName || 'solve';
|
|
5
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parseDateValue(value) {
|
|
9
|
+
if (!value) return null;
|
|
10
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
11
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeExitCode(value) {
|
|
15
|
+
if (value === null || value === undefined) return null;
|
|
16
|
+
const numeric = Number(value);
|
|
17
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getSessionCompletionExitCode({ exitCode = null, statusResult = null } = {}) {
|
|
21
|
+
const explicitExitCode = normalizeExitCode(exitCode);
|
|
22
|
+
if (explicitExitCode !== null) return explicitExitCode;
|
|
23
|
+
|
|
24
|
+
const statusExitCode = normalizeExitCode(statusResult?.exitCode);
|
|
25
|
+
if (statusExitCode !== null) return statusExitCode;
|
|
26
|
+
|
|
27
|
+
const status = String(statusResult?.status || '').toLowerCase();
|
|
28
|
+
if (FAILURE_STATUSES.has(status)) return 1;
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatSessionDurationSeconds(seconds) {
|
|
34
|
+
const totalSeconds = Math.max(0, Math.round(Number(seconds) || 0));
|
|
35
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
36
|
+
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
37
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
38
|
+
const remainingSeconds = totalSeconds % 60;
|
|
39
|
+
const parts = [];
|
|
40
|
+
|
|
41
|
+
if (days > 0) parts.push(`${days}d`);
|
|
42
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
43
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
44
|
+
if (remainingSeconds > 0 || parts.length === 0) parts.push(`${remainingSeconds}s`);
|
|
45
|
+
|
|
46
|
+
return parts.join(' ');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatExecutingWorkSessionMessage({ commandName = 'solve', sessionName = 'unknown', isolationBackend = null, infoBlock = '' } = {}) {
|
|
50
|
+
const isolationInfo = isolationBackend ? `\n🔒 Isolation: \`${isolationBackend}\`` : '';
|
|
51
|
+
const details = infoBlock ? `\n\n${infoBlock}` : '';
|
|
52
|
+
return `⏳ ${capitalizeCommandName(commandName)} command executing...\n\n📊 Session: \`${sessionName}\`${isolationInfo}${details}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null } = {}) {
|
|
56
|
+
const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
|
|
57
|
+
const failed = finalExitCode !== null && finalExitCode !== 0;
|
|
58
|
+
const statusEmoji = failed ? '❌' : '✅';
|
|
59
|
+
const statusText = failed ? `Failed (exit code: ${finalExitCode})` : 'Completed';
|
|
60
|
+
const isolationInfo = sessionInfo?.isolationBackend ? `\n🔒 Isolation: ${sessionInfo.isolationBackend}` : '';
|
|
61
|
+
const startTime = parseDateValue(statusResult?.startTime) || parseDateValue(sessionInfo?.startTime) || observedEndTime;
|
|
62
|
+
const endTime = parseDateValue(statusResult?.endTime) || observedEndTime;
|
|
63
|
+
const durationSeconds = Math.max(0, (endTime.getTime() - startTime.getTime()) / 1000);
|
|
64
|
+
|
|
65
|
+
let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
|
|
66
|
+
message += `📊 Session: \`${sessionName || 'unknown'}\`\n`;
|
|
67
|
+
message += `⏱️ Duration: ${formatSessionDurationSeconds(durationSeconds)}\n`;
|
|
68
|
+
message += `🔗 URL: ${sessionInfo?.url || 'unknown'}${isolationInfo}\n\n`;
|
|
69
|
+
message += 'The work session has finished. You can now review the results.';
|
|
70
|
+
|
|
71
|
+
return message;
|
|
72
|
+
}
|