@link-assistant/hive-mind 1.69.4 → 1.69.6
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 +12 -0
- package/package.json +3 -2
- package/src/auto-language.lib.mjs +94 -0
- package/src/limits-i18n.lib.mjs +71 -0
- package/src/limits.lib.mjs +78 -80
- package/src/locales/en.lino +124 -0
- package/src/locales/hi.lino +124 -0
- package/src/locales/ru.lino +124 -0
- package/src/locales/zh.lino +124 -0
- package/src/session-monitor.lib.mjs +4 -2
- package/src/solve.config.lib.mjs +21 -0
- package/src/solve.mjs +12 -10
- package/src/telegram-bot.mjs +69 -135
- package/src/telegram-command-execution.lib.mjs +5 -4
- package/src/telegram-show-limits.lib.mjs +39 -32
- package/src/telegram-solve-queue.lib.mjs +7 -3
- package/src/telegram-terminal-watch-command.lib.mjs +32 -16
- package/src/telegram-ui-messages.lib.mjs +112 -0
- package/src/work-session-formatting.lib.mjs +33 -14
|
@@ -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,
|
|
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 =
|
|
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
|
}
|
|
@@ -17,7 +17,7 @@ const activeWatches = new Map();
|
|
|
17
17
|
|
|
18
18
|
function splitCommandArgs(text) {
|
|
19
19
|
const body = String(text || '')
|
|
20
|
-
.replace(/^\/terminal_watch(?:@\w+)?\b/i, '')
|
|
20
|
+
.replace(/^\/(?:terminal_watch|watch)(?:@\w+)?\b/i, '')
|
|
21
21
|
.trim();
|
|
22
22
|
return body.match(/"[^"]*"|'[^']*'|\S+/g)?.map(token => token.replace(/^(['"])(.*)\1$/, '$2')) || [];
|
|
23
23
|
}
|
|
@@ -171,6 +171,12 @@ async function querySessionStatusWithRetry(querySessionStatus, sessionId, verbos
|
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
export function isTerminalWatchSessionRequester({ sessionInfo = null, userId = null } = {}) {
|
|
175
|
+
if (userId === null || userId === undefined) return false;
|
|
176
|
+
if (sessionInfo?.requesterUserId === null || sessionInfo?.requesterUserId === undefined) return false;
|
|
177
|
+
return String(sessionInfo.requesterUserId) === String(userId);
|
|
178
|
+
}
|
|
179
|
+
|
|
174
180
|
// Note: /terminal_watch never uploads the full session log itself (issue #1720).
|
|
175
181
|
// Use /log <uuid> if you want the log file delivered as a document.
|
|
176
182
|
function getDisplayedTerminalSnapshot(logText, options) {
|
|
@@ -229,7 +235,7 @@ export function watchTerminalLogSession({ bot, chatId, messageId, sessionId, log
|
|
|
229
235
|
}
|
|
230
236
|
|
|
231
237
|
function buildUsage() {
|
|
232
|
-
return 'Usage:\n• `/terminal_watch <UUID
|
|
238
|
+
return 'Usage:\n• `/terminal_watch <UUID>` (alias: `/watch <UUID>`)\n• Reply to a session message with `/terminal_watch` or `/watch`\n\nOptions: `--size 120x25`, `--width 120`, `--height 25`, `--interval-ms 2500`, `--max-chars 3400`';
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
async function createWatchMessage({ ctx, targetChatId, replyToMessageId, text }) {
|
|
@@ -302,12 +308,14 @@ export async function startAutoTerminalWatchForSession({ bot, ctx, sessionId, se
|
|
|
302
308
|
|
|
303
309
|
export async function registerTerminalWatchCommand(bot, options) {
|
|
304
310
|
const { VERBOSE = false, isOldMessage, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage } = options;
|
|
305
|
-
const runner = await import('./isolation-runner.lib.mjs');
|
|
311
|
+
const runner = !options.querySessionStatus || !options.isTerminalSessionStatus ? await import('./isolation-runner.lib.mjs') : null;
|
|
312
|
+
const querySessionStatus = options.querySessionStatus || runner.querySessionStatus;
|
|
313
|
+
const isTerminalSessionStatus = options.isTerminalSessionStatus || runner.isTerminalSessionStatus;
|
|
306
314
|
const getTrackedSessionInfo = options.getTrackedSessionInfo || (await import('./session-monitor.lib.mjs')).getTrackedSessionInfo;
|
|
307
315
|
const detectRepositoryVisibility = options.detectRepositoryVisibility || (await import('./github.lib.mjs')).detectRepositoryVisibility;
|
|
308
316
|
const parseGitHubUrl = options.parseGitHubUrl || (await import('./github.lib.mjs')).parseGitHubUrl;
|
|
309
317
|
|
|
310
|
-
|
|
318
|
+
const handleTerminalWatchCommand = async ctx => {
|
|
311
319
|
VERBOSE && console.log('[VERBOSE] /terminal_watch command received');
|
|
312
320
|
if (isOldMessage && isOldMessage(ctx)) return;
|
|
313
321
|
|
|
@@ -327,17 +335,23 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
327
335
|
return;
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
const sessionInfo = getTrackedSessionInfo ? getTrackedSessionInfo(sessionId) : null;
|
|
339
|
+
|
|
330
340
|
if (chat.type !== 'private') {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
341
|
+
if (isTerminalWatchSessionRequester({ sessionInfo, userId: ctx.from?.id })) {
|
|
342
|
+
VERBOSE && console.log(`[VERBOSE] /terminal_watch allowed for session requester ${ctx.from?.id} on ${sessionId}`);
|
|
343
|
+
} else {
|
|
344
|
+
try {
|
|
345
|
+
const member = await ctx.telegram.getChatMember(chat.id, ctx.from.id);
|
|
346
|
+
if (!member || member.status !== 'creator') {
|
|
347
|
+
await ctx.reply('❌ /terminal_watch is only available to the chat owner or the user who started this session.', { reply_to_message_id: message.message_id });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('[ERROR] /terminal_watch: getChatMember failed:', error);
|
|
352
|
+
await ctx.reply('❌ Failed to verify permissions for /terminal_watch.', { reply_to_message_id: message.message_id });
|
|
335
353
|
return;
|
|
336
354
|
}
|
|
337
|
-
} catch (error) {
|
|
338
|
-
console.error('[ERROR] /terminal_watch: getChatMember failed:', error);
|
|
339
|
-
await ctx.reply('❌ Failed to verify permissions for /terminal_watch.', { reply_to_message_id: message.message_id });
|
|
340
|
-
return;
|
|
341
355
|
}
|
|
342
356
|
}
|
|
343
357
|
|
|
@@ -349,7 +363,7 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
349
363
|
|
|
350
364
|
let statusResult;
|
|
351
365
|
try {
|
|
352
|
-
statusResult = await
|
|
366
|
+
statusResult = await querySessionStatus(sessionId, VERBOSE);
|
|
353
367
|
} catch (error) {
|
|
354
368
|
console.error('[ERROR] /terminal_watch: querySessionStatus failed:', error);
|
|
355
369
|
await ctx.reply(`❌ Failed to query session status: ${error.message || String(error)}`, { reply_to_message_id: message.message_id });
|
|
@@ -361,7 +375,6 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
361
375
|
return;
|
|
362
376
|
}
|
|
363
377
|
|
|
364
|
-
const sessionInfo = getTrackedSessionInfo ? getTrackedSessionInfo(sessionId) : null;
|
|
365
378
|
const { repoVisibility, repoDescription } = await resolveTerminalWatchRepository({ sessionInfo, statusResult, parseGitHubUrl, detectRepositoryVisibility });
|
|
366
379
|
const decision = decideLogDestination({ statusResult, sessionInfo, repoVisibility, chatType: chat.type });
|
|
367
380
|
if (decision.destination === 'reject') {
|
|
@@ -378,13 +391,16 @@ export async function registerTerminalWatchCommand(bot, options) {
|
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
try {
|
|
381
|
-
await startWatchFromResolvedSession({ bot, ctx, sessionId, statusResult, sessionInfo, decision, logPath, watchOptions: parsedArgs.options, querySessionStatus
|
|
394
|
+
await startWatchFromResolvedSession({ bot, ctx, sessionId, statusResult, sessionInfo, decision, logPath, watchOptions: parsedArgs.options, querySessionStatus, isTerminalSessionStatus, repoDescription, verbose: VERBOSE });
|
|
382
395
|
} catch (error) {
|
|
383
396
|
console.error('[ERROR] /terminal_watch: failed to start watch:', error);
|
|
384
397
|
const friendly = error?.code === 403 || /chat not found|bot can't initiate conversation/i.test(error?.message || '') ? 'I could not send you a DM. Please open a private chat with me and send /start, then try again.' : `Failed to start terminal watch: ${error.message || String(error)}`;
|
|
385
398
|
await ctx.reply(`❌ ${friendly}`, { reply_to_message_id: message.message_id });
|
|
386
399
|
}
|
|
387
|
-
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
bot.command('terminal_watch', handleTerminalWatchCommand);
|
|
403
|
+
bot.command('watch', handleTerminalWatchCommand);
|
|
388
404
|
}
|
|
389
405
|
|
|
390
406
|
export const __INTERNAL_FOR_TESTS__ = {
|
|
@@ -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
|
|
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
|
|
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
|
|
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 (
|
|
88
|
+
if (urlLabels.some(label => lines[i].startsWith(`${label}: `))) {
|
|
74
89
|
lastUrlLineIdx = i;
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
|
-
const prLine =
|
|
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
|
|
91
|
-
const
|
|
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 += `⏱️
|
|
103
|
-
message += `📊
|
|
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.
|