@link-assistant/hive-mind 1.69.4 → 1.69.5

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.
@@ -21,6 +21,7 @@ import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunni
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';
24
+ import { t } from './i18n.lib.mjs';
24
25
 
25
26
  export const QueueItemStatus = {
26
27
  QUEUED: 'queued',
@@ -51,6 +52,7 @@ class SolveQueueItem {
51
52
  // Issue #594: --show-limits virtual option (hive-telegram-bot only).
52
53
  this.showLimits = options.showLimits === true;
53
54
  this.limitsAtStart = options.limitsAtStart || null;
55
+ this.locale = options.locale || null;
54
56
  this.createdAt = new Date();
55
57
  this.startedAt = null;
56
58
  this.status = QueueItemStatus.QUEUED;
@@ -1094,7 +1096,7 @@ export class SolveQueue {
1094
1096
  this.stats.totalStarted++;
1095
1097
 
1096
1098
  // Update message to show Starting status
1097
- await this.updateItemMessage(item, formatStartingWorkSessionMessage({ infoBlock: item.infoBlock }));
1099
+ await this.updateItemMessage(item, formatStartingWorkSessionMessage({ infoBlock: item.infoBlock, locale: item.locale }));
1098
1100
 
1099
1101
  this.log(`Starting: ${item.toString()} from ${tool} queue`);
1100
1102
 
@@ -1142,7 +1144,7 @@ export class SolveQueue {
1142
1144
 
1143
1145
  if (shouldUpdate) {
1144
1146
  const position = i + 1; // Position within this tool's queue
1145
- await this.updateItemMessage(item, `⏳ Waiting (${tool} queue #${position})\n\n${item.infoBlock}\n\n*Reason:*\n${item.waitingReason}`);
1147
+ 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}`);
1146
1148
  }
1147
1149
  }
1148
1150
  }
@@ -1186,10 +1188,11 @@ export class SolveQueue {
1186
1188
  sessionName,
1187
1189
  isolationBackend: result.isolationBackend,
1188
1190
  infoBlock: item.infoBlock,
1191
+ locale: item.locale,
1189
1192
  });
1190
1193
  await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
1191
1194
  } else {
1192
- const response = `❌ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\`\n\n${item.infoBlock}`;
1195
+ const response = `${t('telegram.error_executing_command', { commandName: 'solve' }, { locale: item.locale })}:\n\n\`\`\`\n${result.error || result.output}\n\`\`\`\n\n${item.infoBlock}`;
1193
1196
  await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
1194
1197
  }
1195
1198
  } catch (error) {
@@ -1415,6 +1418,7 @@ export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
1415
1418
  // handler so it can render an end-of-task delta.
1416
1419
  showLimits: item.showLimits === true,
1417
1420
  limitsAtStart: item.limitsAtStart || null,
1421
+ locale: item.locale || null,
1418
1422
  });
1419
1423
  }
1420
1424
  }
@@ -0,0 +1,112 @@
1
+ import { t } from './i18n.lib.mjs';
2
+
3
+ function tr(key, params = {}, locale = null) {
4
+ return t(key, params, { locale });
5
+ }
6
+
7
+ function addLine(parts, key, params, locale) {
8
+ parts.push(tr(key, params, locale));
9
+ }
10
+
11
+ export function buildTelegramInfoBlock({ locale = null, requester = '', urlKind = 'url', url = '', optionsRaw = '', lockedOptions = '' } = {}) {
12
+ const labelKey = urlKind === 'issue' ? 'telegram.info_issue_label' : urlKind === 'pullRequest' ? 'telegram.info_pull_request_label' : 'telegram.info_url_label';
13
+ let infoBlock = `${tr('telegram.info_requested_by_label', {}, locale)}: ${requester}\n${tr(labelKey, {}, locale)}: ${url}`;
14
+ if (optionsRaw) infoBlock += `\n\n${tr('telegram.info_options_label', {}, locale)}: ${optionsRaw}`;
15
+ if (lockedOptions) infoBlock += `${optionsRaw ? '\n' : '\n\n'}${tr('telegram.info_locked_options_label', {}, locale)}: ${lockedOptions}`;
16
+ return infoBlock;
17
+ }
18
+
19
+ export function buildSolveQueuedMessage({ locale = null, tool = 'claude', position = 1, infoBlock = '', reason = '' } = {}) {
20
+ let message = tr('telegram.solve_queued', { tool, position }, locale);
21
+ if (infoBlock) message += `\n\n${infoBlock}`;
22
+ if (reason) message += `\n\n${tr('telegram.waiting_label', {}, locale)}: ${reason}`;
23
+ return message;
24
+ }
25
+
26
+ export function buildTelegramHelpMessage({ locale = null, chatId, chatType = '', chatTitle = '', topicId = null, isStopped = false, stopInfo = null, stopReason = '', solveEnabled = true, taskEnabled = true, hiveEnabled = true, solveOverrides = [], hiveOverrides = [], showLimitsEnabled = false, isolationBackend = null, modelDescription = '', restrictedMode = false, authorized = null, allowTopicHint = '' } = {}) {
27
+ const message = [];
28
+ addLine(message, 'telegram.help_title', {}, locale);
29
+ message.push('');
30
+
31
+ if (isStopped) {
32
+ addLine(message, 'telegram.help_status_stopped', {}, locale);
33
+ addLine(message, 'telegram.help_reason', { reason: stopReason }, locale);
34
+ if (stopInfo?.stoppedAt) addLine(message, 'telegram.help_stopped', { stoppedAt: stopInfo.stoppedAt.toISOString() }, locale);
35
+ addLine(message, 'telegram.help_resume', {}, locale);
36
+ message.push('');
37
+ }
38
+
39
+ addLine(message, 'telegram.help_diagnostic_information', {}, locale);
40
+ addLine(message, 'telegram.help_chat_id', { chatId }, locale);
41
+ if (topicId) addLine(message, 'telegram.help_topic_id', { topicId }, locale);
42
+ addLine(message, 'telegram.help_chat_type', { chatType }, locale);
43
+ addLine(message, 'telegram.help_chat_title', { chatTitle }, locale);
44
+ message.push('');
45
+ addLine(message, 'telegram.help_available_commands', {}, locale);
46
+ message.push('');
47
+
48
+ if (solveEnabled) {
49
+ addLine(message, 'telegram.help_solve_enabled', {}, locale);
50
+ addLine(message, 'telegram.help_solve_usage', {}, locale);
51
+ addLine(message, 'telegram.help_solve_example', {}, locale);
52
+ addLine(message, 'telegram.help_solve_alias_detail', {}, locale);
53
+ addLine(message, 'telegram.help_solve_reply', {}, locale);
54
+ if (solveOverrides.length > 0) addLine(message, 'telegram.help_locked_options', { options: solveOverrides.join(' ') }, locale);
55
+ message.push('');
56
+ } else {
57
+ addLine(message, 'telegram.help_solve_disabled', {}, locale);
58
+ message.push('');
59
+ }
60
+
61
+ if (taskEnabled) {
62
+ addLine(message, 'telegram.help_task_enabled', {}, locale);
63
+ addLine(message, 'telegram.help_task_usage', {}, locale);
64
+ addLine(message, 'telegram.help_task_example', {}, locale);
65
+ addLine(message, 'telegram.help_split_enabled', {}, locale);
66
+ addLine(message, 'telegram.help_split_usage', {}, locale);
67
+ addLine(message, 'telegram.help_split_example', {}, locale);
68
+ message.push('');
69
+ } else {
70
+ addLine(message, 'telegram.help_task_disabled', {}, locale);
71
+ message.push('');
72
+ }
73
+
74
+ if (hiveEnabled) {
75
+ addLine(message, 'telegram.help_hive_enabled', {}, locale);
76
+ addLine(message, 'telegram.help_hive_usage', {}, locale);
77
+ addLine(message, 'telegram.help_hive_example', {}, locale);
78
+ if (hiveOverrides.length > 0) addLine(message, 'telegram.help_locked_options', { options: hiveOverrides.join(' ') }, locale);
79
+ message.push('');
80
+ } else {
81
+ addLine(message, 'telegram.help_hive_disabled', {}, locale);
82
+ message.push('');
83
+ }
84
+
85
+ const simpleCommandKeys = ['telegram.help_solve_queue', 'telegram.help_limits', 'telegram.help_version', 'telegram.help_language', 'telegram.help_accept_invites', 'telegram.help_merge', 'telegram.help_merge_usage', 'telegram.help_merge_description', 'telegram.help_subscribe', 'telegram.help_help', 'telegram.help_stop_start', 'telegram.help_stop_uuid', 'telegram.help_log', 'telegram.help_terminal_watch'];
86
+ for (const key of simpleCommandKeys) addLine(message, key, {}, locale);
87
+ message.push('');
88
+ addLine(message, 'telegram.help_notifications', {}, locale);
89
+ if (isolationBackend) addLine(message, 'telegram.help_isolation_mode', { isolationBackend }, locale);
90
+ message.push('');
91
+ addLine(message, 'telegram.help_group_note', {}, locale);
92
+ message.push('');
93
+ addLine(message, 'telegram.help_common_options', {}, locale);
94
+ addLine(message, 'telegram.help_model_option', { modelDescription }, locale);
95
+ addLine(message, 'telegram.help_base_branch_option', {}, locale);
96
+ addLine(message, 'telegram.help_think_option', {}, locale);
97
+ addLine(message, 'telegram.help_verbose_option', {}, locale);
98
+ if (showLimitsEnabled) addLine(message, 'telegram.help_show_limits_option', {}, locale);
99
+ addLine(message, 'telegram.help_tip', {}, locale);
100
+
101
+ if (restrictedMode) {
102
+ message.push('');
103
+ addLine(message, 'telegram.help_restricted_mode', { authorized: authorized ? tr('telegram.yes', {}, locale) : tr('telegram.no', {}, locale) }, locale);
104
+ if (!authorized && allowTopicHint) addLine(message, 'telegram.help_allow_topic_hint', { allowTopicHint }, locale);
105
+ }
106
+
107
+ message.push('');
108
+ message.push('');
109
+ addLine(message, 'telegram.help_troubleshooting_header', {}, locale);
110
+ addLine(message, 'telegram.help_troubleshooting_body', {}, locale);
111
+ return message.join('\n');
112
+ }
@@ -1,5 +1,12 @@
1
+ import { t } from './i18n.lib.mjs';
2
+
1
3
  const FAILURE_STATUSES = new Set(['failed', 'cancelled', 'canceled', 'error']);
2
4
 
5
+ function text(locale, key, fallback, params = {}) {
6
+ if (!locale) return fallback;
7
+ return t(key, params, { locale });
8
+ }
9
+
3
10
  function parseDateValue(value) {
4
11
  if (!value) return null;
5
12
  const date = value instanceof Date ? value : new Date(value);
@@ -41,15 +48,17 @@ export function formatSessionDurationSeconds(seconds) {
41
48
  return parts.join(' ');
42
49
  }
43
50
 
44
- export function formatStartingWorkSessionMessage({ infoBlock = '' } = {}) {
51
+ export function formatStartingWorkSessionMessage({ infoBlock = '', locale = null } = {}) {
45
52
  const details = infoBlock ? `\n\n${infoBlock}` : '';
46
- return `🔄 Starting...${details}`;
53
+ return `${text(locale, 'telegram.work_session_starting', '🔄 Starting...')}${details}`;
47
54
  }
48
55
 
49
- export function formatExecutingWorkSessionMessage({ sessionName = 'unknown', isolationBackend = null, infoBlock = '' } = {}) {
50
- const isolationInfo = isolationBackend ? `\n🔒 Isolation: \`${isolationBackend}\`` : '';
56
+ export function formatExecutingWorkSessionMessage({ sessionName = 'unknown', isolationBackend = null, infoBlock = '', locale = null } = {}) {
57
+ const sessionLabel = text(locale, 'telegram.session_label', 'Session');
58
+ const isolationLabel = text(locale, 'telegram.isolation_label', 'Isolation');
59
+ const isolationInfo = isolationBackend ? `\n🔒 ${isolationLabel}: \`${isolationBackend}\`` : '';
51
60
  const details = infoBlock ? `\n\n${infoBlock}` : '';
52
- return `⏳ Executing...\n\n📊 Session: \`${sessionName}\`${isolationInfo}${details}`;
61
+ return `${text(locale, 'telegram.work_session_executing', '⏳ Executing...')}\n\n📊 ${sessionLabel}: \`${sessionName}\`${isolationInfo}${details}`;
53
62
  }
54
63
 
55
64
  /**
@@ -63,18 +72,24 @@ export function formatExecutingWorkSessionMessage({ sessionName = 'unknown', iso
63
72
  *
64
73
  * @see https://github.com/link-assistant/hive-mind/issues/1688
65
74
  */
66
- export function appendPullRequestLine(infoBlock, pullRequestUrl) {
75
+ export function appendPullRequestLine(infoBlock, pullRequestUrl, { locale = null } = {}) {
67
76
  if (!pullRequestUrl || !infoBlock) return infoBlock || '';
68
77
  if (infoBlock.includes(pullRequestUrl)) return infoBlock;
69
78
 
70
79
  const lines = infoBlock.split('\n');
71
80
  let lastUrlLineIdx = -1;
81
+ const urlLabels = ['Issue', 'Pull request', 'URL'];
82
+ if (locale) {
83
+ urlLabels.push(t('telegram.info_issue_label', {}, { locale }));
84
+ urlLabels.push(t('telegram.info_pull_request_label', {}, { locale }));
85
+ urlLabels.push(t('telegram.info_url_label', {}, { locale }));
86
+ }
72
87
  for (let i = 0; i < lines.length; i++) {
73
- if (/^(Issue|Pull request|URL):\s/.test(lines[i])) {
88
+ if (urlLabels.some(label => lines[i].startsWith(`${label}: `))) {
74
89
  lastUrlLineIdx = i;
75
90
  }
76
91
  }
77
- const prLine = `Pull request: ${pullRequestUrl}`;
92
+ const prLine = `${text(locale, 'telegram.info_pull_request_label', 'Pull request')}: ${pullRequestUrl}`;
78
93
  if (lastUrlLineIdx === -1) {
79
94
  return `${infoBlock}\n${prLine}`;
80
95
  }
@@ -83,24 +98,28 @@ export function appendPullRequestLine(infoBlock, pullRequestUrl) {
83
98
  return [...before, prLine, ...after].join('\n');
84
99
  }
85
100
 
86
- export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null, infoBlock = '', pullRequestUrl = null, extraSections = [] } = {}) {
101
+ export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null, infoBlock = '', pullRequestUrl = null, extraSections = [], locale = null } = {}) {
87
102
  const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
88
103
  const failed = finalExitCode !== null && finalExitCode !== 0;
89
104
  const statusEmoji = failed ? '❌' : '✅';
90
- const statusText = failed ? `Work session failed (exit code: ${finalExitCode})` : 'Work session finished successfully';
91
- const isolationInfo = sessionInfo?.isolationBackend ? `\n🔒 Isolation: \`${sessionInfo.isolationBackend}\`` : '';
105
+ const messageLocale = locale || sessionInfo?.locale || null;
106
+ const statusText = failed ? text(messageLocale, 'telegram.work_session_failed', `Work session failed (exit code: ${finalExitCode})`, { exitCode: finalExitCode }) : text(messageLocale, 'telegram.work_session_finished', 'Work session finished successfully');
107
+ const durationLabel = text(messageLocale, 'telegram.duration_label', 'Duration');
108
+ const sessionLabel = text(messageLocale, 'telegram.session_label', 'Session');
109
+ const isolationLabel = text(messageLocale, 'telegram.isolation_label', 'Isolation');
110
+ const isolationInfo = sessionInfo?.isolationBackend ? `\n🔒 ${isolationLabel}: \`${sessionInfo.isolationBackend}\`` : '';
92
111
  const startTime = parseDateValue(statusResult?.startTime) || parseDateValue(sessionInfo?.startTime) || observedEndTime;
93
112
  const endTime = parseDateValue(statusResult?.endTime) || observedEndTime;
94
113
  const durationSeconds = Math.max(0, (endTime.getTime() - startTime.getTime()) / 1000);
95
114
  let resolvedInfoBlock = infoBlock || sessionInfo?.infoBlock || '';
96
115
  // Issue #1688: When the agent created a PR for an issue-driven /solve, append
97
116
  // a 'Pull request:' line so the completion message includes both Issue and PR links.
98
- if (pullRequestUrl) resolvedInfoBlock = appendPullRequestLine(resolvedInfoBlock, pullRequestUrl);
117
+ if (pullRequestUrl) resolvedInfoBlock = appendPullRequestLine(resolvedInfoBlock, pullRequestUrl, { locale: messageLocale });
99
118
  const details = resolvedInfoBlock ? `\n\n${resolvedInfoBlock}` : '';
100
119
 
101
120
  let message = `${statusEmoji} *${statusText}*\n\n`;
102
- message += `⏱️ Duration: ${formatSessionDurationSeconds(durationSeconds)}\n`;
103
- message += `📊 Session: \`${sessionName || 'unknown'}\`${isolationInfo}${details}`;
121
+ message += `⏱️ ${durationLabel}: ${formatSessionDurationSeconds(durationSeconds)}\n`;
122
+ message += `📊 ${sessionLabel}: \`${sessionName || 'unknown'}\`${isolationInfo}${details}`;
104
123
 
105
124
  // Issue #594: --show-limits virtual option appends snapshot/delta sections
106
125
  // (Markdown code blocks) below the standard completion details.