@link-assistant/hive-mind 1.56.14 → 1.56.15

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.15
4
+
5
+ ### Patch Changes
6
+
7
+ - cdd8010: Refine the Telegram bot work-session messages: introduce `šŸ”„ Starting...` and `ā³ Executing...` to distinguish launch from execution, change the completion headline to `āœ… Work session finished successfully` / `āŒ Work session failed (exit code: N)`, show duration before session, and preserve the audit infoBlock (`Requested by`, `URL`, `šŸ›  Options`, `šŸ”’ Locked options`) on every state — including completion and failure paths — so admins keep a record even when users delete their original `/solve` message.
8
+
3
9
  ## 1.56.14
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.56.14",
3
+ "version": "1.56.15",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "hive-telegram-bot": "./src/telegram-bot.mjs"
16
16
  },
17
17
  "scripts": {
18
- "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-auto-restart-limits-1664.mjs && node tests/test-log-upload-output-1678.mjs && node tests/test-log-upload-output-1682.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-options-before-url.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-issue-1670-screen-status-monitoring.mjs && node tests/test-issue-1680-session-monitoring.mjs && node tests/test-telegram-bot-launcher.mjs",
18
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-auto-restart-limits-1664.mjs && node tests/test-log-upload-output-1678.mjs && node tests/test-log-upload-output-1682.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-options-before-url.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-issue-1670-screen-status-monitoring.mjs && node tests/test-issue-1680-session-monitoring.mjs && node tests/test-issue-1684-message-formatting.mjs && node tests/test-telegram-bot-launcher.mjs",
19
19
  "test:queue": "node tests/solve-queue.test.mjs",
20
20
  "test:limits-display": "node tests/limits-display.test.mjs",
21
21
  "test:usage-limit": "node tests/test-usage-limit.mjs",
@@ -257,6 +257,7 @@ export async function monitorSessions(bot, verbose = false, options = {}) {
257
257
  statusResult,
258
258
  observedEndTime: new Date(),
259
259
  exitCode: finalExitCode,
260
+ infoBlock: sessionInfo?.infoBlock || '',
260
261
  });
261
262
 
262
263
  // Update the original reply message if messageId is available, otherwise send new message
@@ -51,7 +51,7 @@ const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STO
51
51
  const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
52
52
  const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
53
53
  const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
54
- const { formatExecutingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
54
+ const { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
55
55
 
56
56
  const config = yargs(hideBin(process.argv))
57
57
  .usage('Usage: hive-telegram-bot [options]')
@@ -566,7 +566,7 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
566
566
  session = iso.runner.generateSessionId();
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
- 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);
569
+ 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, infoBlock }, VERBOSE);
570
570
  } else {
571
571
  result = await executeStartScreen(commandName, args);
572
572
  const match = result.success && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
@@ -575,19 +575,18 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
575
575
  // These sessions cannot reliably detect completion (screen stays alive via
576
576
  // `exec bash`), so active URL checks auto-expire them after 10 min.
577
577
  // This prevents accidental duplicate commands within the timeout window.
578
- if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool }, VERBOSE);
578
+ if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool, infoBlock }, VERBOSE);
579
579
  }
580
580
  if (result.warning) return safeEdit(`āš ļø ${result.warning}`);
581
581
  if (result.success) {
582
582
  await safeEdit(
583
583
  formatExecutingWorkSessionMessage({
584
- commandName,
585
584
  sessionName: session,
586
585
  isolationBackend: iso?.backend || null,
587
586
  infoBlock,
588
587
  })
589
588
  );
590
- } else await safeEdit(`āŒ Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
589
+ } else await safeEdit(`āŒ Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\`\n\n${infoBlock}`);
591
590
  }
592
591
 
593
592
  bot.command('help', async ctx => {
@@ -1012,7 +1011,7 @@ async function handleSolveCommand(ctx) {
1012
1011
 
1013
1012
  const toolQueuedCount = queueStats.queuedByTool[solveTool] || 0; // tool-specific queue count (#1551)
1014
1013
  if (check.canStart && toolQueuedCount === 0) {
1015
- const startingMessage = await safeReply(ctx, `šŸš€ Starting solve command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
1014
+ const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
1016
1015
  await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock, effectiveSolveIsolation, solveTool);
1017
1016
  } else {
1018
1017
  const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation });
@@ -1177,7 +1176,7 @@ async function handleHiveCommand(ctx) {
1177
1176
  infoBlock += `${userOptionsRaw ? '\n' : '\n\n'}šŸ”’ Locked options: ${escapeMarkdown(hiveOverrides.join(' '))}`;
1178
1177
  }
1179
1178
 
1180
- const startingMessage = await safeReply(ctx, `šŸš€ Starting hive command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
1179
+ const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
1181
1180
  await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock, effectiveHiveIsolation, hiveTool);
1182
1181
  }
1183
1182
 
@@ -80,7 +80,7 @@ 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, tool: item.tool || 'claude' }, 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, tool: item.tool || 'claude', infoBlock: item.infoBlock }, verbose);
84
84
  return { ...r, sessionId: sid, isolationBackend: iso.backend, output: r.output || `session: ${sid}` };
85
85
  }
86
86
  return fallbackCallback(item);
@@ -20,7 +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
+ import { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
24
24
 
25
25
  export const QueueItemStatus = {
26
26
  QUEUED: 'queued',
@@ -1058,7 +1058,7 @@ export class SolveQueue {
1058
1058
  this.stats.totalStarted++;
1059
1059
 
1060
1060
  // Update message to show Starting status
1061
- await this.updateItemMessage(item, `šŸš€ Starting solve command...\n\n${item.infoBlock}`);
1061
+ await this.updateItemMessage(item, formatStartingWorkSessionMessage({ infoBlock: item.infoBlock }));
1062
1062
 
1063
1063
  this.log(`Starting: ${item.toString()} from ${tool} queue`);
1064
1064
 
@@ -1144,17 +1144,16 @@ export class SolveQueue {
1144
1144
  if (chatId && messageId) {
1145
1145
  try {
1146
1146
  if (result.warning) {
1147
- await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `āš ļø ${result.warning}`, { parse_mode: 'Markdown' });
1147
+ await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `āš ļø ${result.warning}\n\n${item.infoBlock}`, { parse_mode: 'Markdown' });
1148
1148
  } else if (result.success) {
1149
1149
  const response = formatExecutingWorkSessionMessage({
1150
- commandName: item.command || 'solve',
1151
1150
  sessionName,
1152
1151
  isolationBackend: result.isolationBackend,
1153
1152
  infoBlock: item.infoBlock,
1154
1153
  });
1155
1154
  await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
1156
1155
  } else {
1157
- const response = `āŒ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``;
1156
+ const response = `āŒ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\`\n\n${item.infoBlock}`;
1158
1157
  await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
1159
1158
  }
1160
1159
  } catch (error) {
@@ -1177,7 +1176,8 @@ export class SolveQueue {
1177
1176
  const { chatId, messageId } = item.messageInfo || {};
1178
1177
  if (chatId && messageId && item.ctx) {
1179
1178
  try {
1180
- await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `āŒ Error: ${error.message}`, { parse_mode: 'Markdown' });
1179
+ const errorText = item.infoBlock ? `āŒ Error: ${error.message}\n\n${item.infoBlock}` : `āŒ Error: ${error.message}`;
1180
+ await item.ctx.telegram.editMessageText(chatId, messageId, undefined, errorText, { parse_mode: 'Markdown' });
1181
1181
  } catch (editError) {
1182
1182
  // Log the edit failure for debugging
1183
1183
  // See: https://github.com/link-assistant/hive-mind/issues/1062
@@ -1361,7 +1361,7 @@ export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
1361
1361
  const match = result.output && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
1362
1362
  const session = match ? match[1] : null;
1363
1363
  if (session) {
1364
- trackSessionFn(session, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve', tool: item.tool || 'claude' });
1364
+ trackSessionFn(session, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve', tool: item.tool || 'claude', infoBlock: item.infoBlock });
1365
1365
  }
1366
1366
  }
1367
1367
  return result;
@@ -1,10 +1,5 @@
1
1
  const FAILURE_STATUSES = new Set(['failed', 'cancelled', 'canceled', 'error']);
2
2
 
3
- function capitalizeCommandName(commandName) {
4
- const normalized = commandName || 'solve';
5
- return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6
- }
7
-
8
3
  function parseDateValue(value) {
9
4
  if (!value) return null;
10
5
  const date = value instanceof Date ? value : new Date(value);
@@ -46,27 +41,32 @@ export function formatSessionDurationSeconds(seconds) {
46
41
  return parts.join(' ');
47
42
  }
48
43
 
49
- export function formatExecutingWorkSessionMessage({ commandName = 'solve', sessionName = 'unknown', isolationBackend = null, infoBlock = '' } = {}) {
44
+ export function formatStartingWorkSessionMessage({ infoBlock = '' } = {}) {
45
+ const details = infoBlock ? `\n\n${infoBlock}` : '';
46
+ return `šŸ”„ Starting...${details}`;
47
+ }
48
+
49
+ export function formatExecutingWorkSessionMessage({ sessionName = 'unknown', isolationBackend = null, infoBlock = '' } = {}) {
50
50
  const isolationInfo = isolationBackend ? `\nšŸ”’ Isolation: \`${isolationBackend}\`` : '';
51
51
  const details = infoBlock ? `\n\n${infoBlock}` : '';
52
- return `ā³ ${capitalizeCommandName(commandName)} command executing...\n\nšŸ“Š Session: \`${sessionName}\`${isolationInfo}${details}`;
52
+ return `ā³ Executing...\n\nšŸ“Š Session: \`${sessionName}\`${isolationInfo}${details}`;
53
53
  }
54
54
 
55
- export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null } = {}) {
55
+ export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null, infoBlock = '' } = {}) {
56
56
  const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
57
57
  const failed = finalExitCode !== null && finalExitCode !== 0;
58
58
  const statusEmoji = failed ? 'āŒ' : 'āœ…';
59
- const statusText = failed ? `Failed (exit code: ${finalExitCode})` : 'Completed';
60
- const isolationInfo = sessionInfo?.isolationBackend ? `\nšŸ”’ Isolation: ${sessionInfo.isolationBackend}` : '';
59
+ const statusText = failed ? `Work session failed (exit code: ${finalExitCode})` : 'Work session finished successfully';
60
+ const isolationInfo = sessionInfo?.isolationBackend ? `\nšŸ”’ Isolation: \`${sessionInfo.isolationBackend}\`` : '';
61
61
  const startTime = parseDateValue(statusResult?.startTime) || parseDateValue(sessionInfo?.startTime) || observedEndTime;
62
62
  const endTime = parseDateValue(statusResult?.endTime) || observedEndTime;
63
63
  const durationSeconds = Math.max(0, (endTime.getTime() - startTime.getTime()) / 1000);
64
+ const resolvedInfoBlock = infoBlock || sessionInfo?.infoBlock || '';
65
+ const details = resolvedInfoBlock ? `\n\n${resolvedInfoBlock}` : '';
64
66
 
65
- let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
66
- message += `šŸ“Š Session: \`${sessionName || 'unknown'}\`\n`;
67
+ let message = `${statusEmoji} *${statusText}*\n\n`;
67
68
  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.';
69
+ message += `šŸ“Š Session: \`${sessionName || 'unknown'}\`${isolationInfo}${details}`;
70
70
 
71
71
  return message;
72
72
  }