@link-assistant/hive-mind 1.73.6 → 1.73.7

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,23 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.73.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 6188172: feat(telegram): list executed issues/PRs as clickable links in /solve_queue, add /queue alias (#1837)
8
+
9
+ The `/solve_queue` detailed status previously showed only per-tool counts and a
10
+ final `Completed: N, Failed: M` line, so a stuck or running task could not be
11
+ opened from the message. It now lists each processing (`▶️`), pending (`•`),
12
+ recently completed (`✅`), and failed (`❌`, with the error reason) item as a
13
+ clickable `[owner/repo#number](url)` link, capped per section
14
+ (`HIVE_MIND_MAX_DISPLAY_ITEMS_PER_QUEUE`, default 5) with a localized
15
+ `... and N more` line to stay under Telegram's 4096-character limit.
16
+
17
+ Also adds `/queue` as a shorter alias for `/solve_queue` (both the entity-based
18
+ command regex and the text-based fallback handler), and documents the work in
19
+ `docs/case-studies/issue-1837`.
20
+
3
21
  ## 1.73.6
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.73.6",
3
+ "version": "1.73.7",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -515,7 +515,7 @@ en
515
515
  detail "Tool aliases imply `--tool <tool>`: `/codex <github-url>` equals `/solve <github-url> --tool codex`"
516
516
  reply "Or reply to a message with a GitHub link: `/solve`"
517
517
  disabled "*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ Disabled"
518
- queue "`/solve_queue` - Show solve queue status"
518
+ queue "`/solve_queue` (alias: `/queue`) - Show solve queue status"
519
519
  locked
520
520
  options "🔒 Locked options: `{{options}}`"
521
521
  task
@@ -515,7 +515,7 @@ hi
515
515
  detail "Tool aliases `--tool <tool>` लगाते हैं: `/codex <github-url>` का अर्थ `/solve <github-url> --tool codex` है"
516
516
  reply "या GitHub लिंक वाले संदेश का उत्तर दें: `/solve`"
517
517
  disabled "*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ अक्षम"
518
- queue "`/solve_queue` - solve queue status दिखाएँ"
518
+ queue "`/solve_queue` (उपनाम: `/queue`) - solve queue status दिखाएँ"
519
519
  locked
520
520
  options "🔒 लॉक किए गए विकल्प: `{{options}}`"
521
521
  task
@@ -515,7 +515,7 @@ ru
515
515
  detail "Алиасы инструментов добавляют `--tool <tool>`: `/codex <github-url>` равно `/solve <github-url> --tool codex`"
516
516
  reply "Или ответьте на сообщение со ссылкой GitHub: `/solve`"
517
517
  disabled "*/solve* (алиасы: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ Отключено"
518
- queue "`/solve_queue` - Показать состояние очереди solve"
518
+ queue "`/solve_queue` (псевдоним: `/queue`) - Показать состояние очереди solve"
519
519
  locked
520
520
  options "🔒 Заблокированные опции: `{{options}}`"
521
521
  task
@@ -515,7 +515,7 @@ zh
515
515
  detail "工具别名会添加 `--tool <tool>`:`/codex <github-url>` 等同于 `/solve <github-url> --tool codex`"
516
516
  reply "也可以回复包含 GitHub 链接的消息:`/solve`"
517
517
  disabled "*/solve*(别名:*/do*、*/continue*、*/claude*、*/codex*、*/opencode*、*/agent*、*/gemini*、*/qwen*)- ❌ 已禁用"
518
- queue "`/solve_queue` - 显示 solve 队列状态"
518
+ queue "`/solve_queue`(别名:`/queue`)- 显示 solve 队列状态"
519
519
  locked
520
520
  options "🔒 锁定选项:`{{options}}`"
521
521
  task
@@ -280,6 +280,13 @@ export const QUEUE_CONFIG = {
280
280
 
281
281
  // Process detection
282
282
  CLAUDE_PROCESS_NAMES: ['claude'], // Process names to detect
283
+
284
+ // Display
285
+ // Maximum number of items shown per section (pending/processing/completed/failed)
286
+ // in the /solve_queue (/queue) detailed status before collapsing into a
287
+ // "... and N more" line. Keeps the Telegram message under the 4096-char cap.
288
+ // See: https://github.com/link-assistant/hive-mind/issues/1837
289
+ MAX_DISPLAY_ITEMS_PER_QUEUE: parseIntWithDefault('HIVE_MIND_MAX_DISPLAY_ITEMS_PER_QUEUE', 5),
283
290
  };
284
291
 
285
292
  /**
@@ -1169,7 +1169,8 @@ bot.on('message', async (ctx, next) => {
1169
1169
  // /subscribe + /unsubscribe (#1688) are intentionally not in the text fallback — Telegraf's bot.command() is sufficient.
1170
1170
  const solveHandlers = Object.fromEntries(SOLVE_COMMAND_NAMES.map(command => [command, handleSolveCommand]));
1171
1171
  const taskHandlers = Object.fromEntries(TASK_COMMAND_NAMES.map(command => [command, handleTaskCommand]));
1172
- const handlers = { ...solveHandlers, ...taskHandlers, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand };
1172
+ // /queue is the short alias for /solve_queue (issue #1837)
1173
+ const handlers = { ...solveHandlers, ...taskHandlers, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand, queue: handleSolveQueueCommand };
1173
1174
 
1174
1175
  const handler = handlers[extracted.command];
1175
1176
  if (!handler) return next();
@@ -97,11 +97,12 @@ export function registerSolveQueueCommand(bot, options) {
97
97
  });
98
98
  }
99
99
 
100
- // Match /solve_queue, /solve-queue, or /solvequeue (case-insensitive)
100
+ // Match /solve_queue, /solve-queue, /solvequeue, or the short /queue alias (case-insensitive)
101
101
  // Note: Telegram Bot API only supports underscores in command names, not hyphens.
102
- // The entity-based matching handles /solve_queue and /solvequeue.
102
+ // The entity-based matching handles /solve_queue, /solvequeue, and /queue.
103
103
  // /solve-queue is handled by the text-based fallback in telegram-bot.mjs (issue #1232).
104
- bot.command(/^solve[_-]?queue$/i, handleSolveQueueCommand);
104
+ // The /queue alias was added in issue #1837 to make checking the queue faster to type.
105
+ bot.command(/^(?:solve[_-]?queue|queue)$/i, handleSolveQueueCommand);
105
106
 
106
107
  return { handleSolveQueueCommand };
107
108
  }
@@ -6,6 +6,64 @@ import { lt } from './limits-i18n.lib.mjs';
6
6
 
7
7
  const execAsync = promisify(exec);
8
8
 
9
+ /**
10
+ * Build a clickable, human-readable link to a queued issue/PR for the
11
+ * /solve_queue (/queue) detailed status (issue #1837).
12
+ *
13
+ * For GitHub issue/PR URLs we render a compact `[owner/repo#number](url)`
14
+ * Markdown link so the list is scannable and clickable. When the label would
15
+ * contain Markdown-special characters (e.g. `_` or `*` in an owner/repo name)
16
+ * that could break Telegram's legacy Markdown parser, we fall back to the bare
17
+ * URL — which Telegram still auto-links and renders as clickable.
18
+ *
19
+ * Non-GitHub or unparseable URLs also fall back to the bare URL.
20
+ *
21
+ * @param {string} url - The issue/PR URL.
22
+ * @returns {string} A Markdown link or bare URL safe for `parse_mode: 'Markdown'`.
23
+ */
24
+ export function formatQueueItemLink(url) {
25
+ if (!url || typeof url !== 'string') return String(url ?? '');
26
+ const match = url.match(/github\.com\/([^/\s]+)\/([^/\s]+)\/(?:issues|pull)\/(\d+)/i);
27
+ if (!match) return url;
28
+ const [, owner, repo, number] = match;
29
+ const label = `${owner}/${repo}#${number}`;
30
+ // Only build a Markdown link when the label has no Markdown-special chars
31
+ // that would break the legacy parser inside link text. Otherwise the bare
32
+ // URL is still clickable in Telegram.
33
+ if (/^[A-Za-z0-9/#.-]+$/.test(label)) {
34
+ return `[${label}](${url})`;
35
+ }
36
+ return url;
37
+ }
38
+
39
+ /**
40
+ * Render a history section (Completed / Failed) for the detailed queue status
41
+ * as a clickable list, most-recent-first, capped at `max` items with a
42
+ * "... and N more" line (issue #1837).
43
+ *
44
+ * @param {object} opts
45
+ * @param {Array} opts.items - History items (each with `url`, optional `error`).
46
+ * @param {string} opts.emoji - Leading emoji for each row (e.g. '✅' or '❌').
47
+ * @param {string} opts.label - Localized section heading.
48
+ * @param {number} opts.max - Maximum items to list before collapsing.
49
+ * @param {string|null} opts.locale - Locale for the "and N more" label.
50
+ * @param {boolean} [opts.withError] - Append `— error` when the item failed.
51
+ * @returns {string} The formatted section (empty string when no items).
52
+ */
53
+ export function formatQueueHistorySection({ items, emoji, label, max, locale, withError = false }) {
54
+ if (!items || items.length === 0) return '';
55
+ let section = `*${label}* (${items.length}):\n`;
56
+ for (const item of [...items].reverse().slice(0, max)) {
57
+ section += ` ${emoji} ${formatQueueItemLink(item.url)}`;
58
+ if (withError && item.error) section += ` — ${item.error}`;
59
+ section += '\n';
60
+ }
61
+ if (items.length > max) {
62
+ section += ` ... ${lt('queue_and_more', { count: items.length - max }, { locale })}\n`;
63
+ }
64
+ return `${section}\n`;
65
+ }
66
+
9
67
  /**
10
68
  * Count running processes by name.
11
69
  * @param {string} processName - Process name to search for (e.g., 'claude', 'agent', 'codex', 'gemini')
@@ -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, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
20
- import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
20
+ import { formatDuration, formatQueueHistorySection, formatQueueItemLink, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } 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
  import { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
@@ -1320,28 +1320,17 @@ export class SolveQueue {
1320
1320
  }
1321
1321
 
1322
1322
  /**
1323
- * Format detailed queue status for Telegram message
1324
- * Groups output by tool queue, shows first 5 items per queue, and uses human-readable time.
1323
+ * Format detailed queue status for Telegram message.
1324
+ * Groups output by tool queue (clickable links per item), then lists the
1325
+ * Completed and Failed history as clickable links, capped per section.
1325
1326
  *
1326
1327
  * Processing count = max(actual AI CLI processes via pgrep, tracked
1327
1328
  * `$ --status` executing screen-isolated sessions), not queue state.
1328
1329
  *
1329
- * Output format:
1330
- * ```
1331
- * 📋 Solve Queue Status
1332
- *
1333
- * claude (pending: 6, processing: 0)
1334
- * • url1 (waiting, 5h 43m 23s)
1335
- * └ RAM usage is 70% (threshold: 65%)
1336
- * • url2 (queued, 2m 15s)
1337
- *
1338
- * agent (pending: 2, processing: 0)
1339
- * • url3 (waiting, 1h 2m 5s)
1340
- * ```
1341
- *
1342
1330
  * @returns {Promise<string>}
1343
1331
  * @see https://github.com/link-assistant/hive-mind/issues/1159
1344
1332
  * @see https://github.com/link-assistant/hive-mind/issues/1267
1333
+ * @see https://github.com/link-assistant/hive-mind/issues/1837
1345
1334
  */
1346
1335
  async formatDetailedStatus(options = {}) {
1347
1336
  const locale = getLocale(options);
@@ -1359,22 +1348,37 @@ export class SolveQueue {
1359
1348
  const processing = externalProcessing.byTool[tool] || 0;
1360
1349
  message += `*${tool}* (${lt('queue_pending', {}, { locale })}: ${pending}, ${lt('queue_processing', {}, { locale })}: ${processing})\n`;
1361
1350
 
1362
- // Show first 5 queued items for this tool
1363
- const displayItems = toolQueue.slice(0, 5);
1351
+ // Show the items this queue is actively processing for this tool, with a
1352
+ // clickable link to each issue/PR (issue #1837). These come from the
1353
+ // queue's own tracking, so they may differ from the pgrep-based count above.
1354
+ const processingItems = Array.from(this.processing.values()).filter(item => item.tool === tool);
1355
+ for (const item of processingItems.slice(0, QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE)) {
1356
+ const waitTime = formatDuration(item.getWaitTime(), { locale });
1357
+ message += ` ▶️ ${formatQueueItemLink(item.url)} (${queueStatusLabel(item.status, locale)}, ${waitTime})\n`;
1358
+ }
1359
+
1360
+ // Show first queued items for this tool with clickable links
1361
+ const displayItems = toolQueue.slice(0, QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE);
1364
1362
  for (const item of displayItems) {
1365
1363
  const waitTime = formatDuration(item.getWaitTime(), { locale });
1366
- message += ` • ${item.url} (${queueStatusLabel(item.status, locale)}, ${waitTime})\n`;
1364
+ message += ` • ${formatQueueItemLink(item.url)} (${queueStatusLabel(item.status, locale)}, ${waitTime})\n`;
1367
1365
  if (item.waitingReason) {
1368
1366
  message += ` └ ${item.waitingReason}\n`;
1369
1367
  }
1370
1368
  }
1371
- if (toolQueue.length > 5) {
1372
- message += ` ... ${lt('queue_and_more', { count: toolQueue.length - 5 }, { locale })}\n`;
1369
+ if (toolQueue.length > QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE) {
1370
+ message += ` ... ${lt('queue_and_more', { count: toolQueue.length - QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE }, { locale })}\n`;
1373
1371
  }
1374
1372
 
1375
1373
  message += '\n';
1376
1374
  }
1377
1375
 
1376
+ // Completed / Failed lists - clickable links to the executed issues/PRs,
1377
+ // most-recent-first so the newest results are easy to find (issue #1837).
1378
+ const max = QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE;
1379
+ message += formatQueueHistorySection({ items: this.completed, emoji: '✅', label: lt('queue_completed', {}, { locale }), max, locale });
1380
+ message += formatQueueHistorySection({ items: this.failed, emoji: '❌', label: lt('queue_failed', {}, { locale }), max, locale, withError: true });
1381
+
1378
1382
  // Summary stats
1379
1383
  message += `${lt('queue_completed', {}, { locale })}: ${stats.completed}, ${lt('queue_failed', {}, { locale })}: ${stats.failed}\n`;
1380
1384