@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.69.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c2c51fa: Allow Telegram `/terminal_watch` and `/watch` to be used by the user who started a tracked session while preserving chat-owner access and private-repository DM routing.
|
|
8
|
+
|
|
9
|
+
## 1.69.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 31b0f7e: Add issue language auto-detection for solve work prompts and localize limits output.
|
|
14
|
+
|
|
3
15
|
## 1.69.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.69.
|
|
3
|
+
"version": "1.69.6",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"lino-arguments": "^0.3.0",
|
|
79
79
|
"lino-objects-codec": "^0.3.6",
|
|
80
80
|
"secretlint": "^11.2.5",
|
|
81
|
-
"semver": "^7.7.3"
|
|
81
|
+
"semver": "^7.7.3",
|
|
82
|
+
"tinyld": "^1.3.4"
|
|
82
83
|
},
|
|
83
84
|
"lint-staged": {
|
|
84
85
|
"*.{js,mjs,json,md}": [
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { detect as detectLanguage } from 'tinyld';
|
|
2
|
+
|
|
3
|
+
const WORD_PATTERN = /\p{L}[\p{L}\p{M}'-]*/gu;
|
|
4
|
+
const TARGET_LANGUAGES = new Set(['en', 'ru']);
|
|
5
|
+
const DEFAULT_THRESHOLD = 0.51;
|
|
6
|
+
|
|
7
|
+
export function extractLanguageWords(text) {
|
|
8
|
+
if (!text || typeof text !== 'string') return [];
|
|
9
|
+
return text.match(WORD_PATTERN) || [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function detectByScript(word) {
|
|
13
|
+
const hasCyrillic = /\p{Script=Cyrillic}/u.test(word);
|
|
14
|
+
const hasLatin = /\p{Script=Latin}/u.test(word);
|
|
15
|
+
if (hasCyrillic && !hasLatin) return 'ru';
|
|
16
|
+
if (hasLatin && !hasCyrillic) return 'en';
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function detectWordLanguage(word, detector = detectLanguage) {
|
|
21
|
+
if (!word || typeof word !== 'string') return null;
|
|
22
|
+
let detected = null;
|
|
23
|
+
try {
|
|
24
|
+
detected = detector(word);
|
|
25
|
+
} catch {
|
|
26
|
+
detected = null;
|
|
27
|
+
}
|
|
28
|
+
if (TARGET_LANGUAGES.has(detected)) return detected;
|
|
29
|
+
return detectByScript(word);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function detectIssueLanguageFromText(text, { detector = detectLanguage, threshold = DEFAULT_THRESHOLD, fallbackLanguage = 'en' } = {}) {
|
|
33
|
+
const words = extractLanguageWords(text);
|
|
34
|
+
const counts = { en: 0, ru: 0, detected: 0, ignored: 0, total: words.length };
|
|
35
|
+
|
|
36
|
+
for (const word of words) {
|
|
37
|
+
const language = detectWordLanguage(word, detector);
|
|
38
|
+
if (language === 'en' || language === 'ru') {
|
|
39
|
+
counts[language] += 1;
|
|
40
|
+
counts.detected += 1;
|
|
41
|
+
} else {
|
|
42
|
+
counts.ignored += 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ratios = {
|
|
47
|
+
en: counts.total > 0 ? counts.en / counts.total : 0,
|
|
48
|
+
ru: counts.total > 0 ? counts.ru / counts.total : 0,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let language = fallbackLanguage;
|
|
52
|
+
if (ratios.ru > threshold) {
|
|
53
|
+
language = 'ru';
|
|
54
|
+
} else if (ratios.en > threshold) {
|
|
55
|
+
language = 'en';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { language, counts, ratios, threshold };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function fetchTargetTextForAutoLanguage({ githubLib, owner, repo, number, isIssueUrl, isPrUrl }) {
|
|
62
|
+
const jsonFields = 'number,title,body';
|
|
63
|
+
let result = null;
|
|
64
|
+
if (isIssueUrl) {
|
|
65
|
+
result = await githubLib.ghIssueView({ issueNumber: number, owner, repo, jsonFields });
|
|
66
|
+
} else if (isPrUrl) {
|
|
67
|
+
result = await githubLib.ghPrView({ prNumber: number, owner, repo, jsonFields });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!result || result.code !== 0 || !result.data) return null;
|
|
71
|
+
return [result.data.title, result.data.body].filter(Boolean).join('\n\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function applyAutoLanguageToArgv({ argv, githubLib, owner, repo, number, isIssueUrl, isPrUrl, log = async () => {} }) {
|
|
75
|
+
if (!argv?.autoLanguage || argv._workLanguageExplicit) return null;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const text = await fetchTargetTextForAutoLanguage({ githubLib, owner, repo, number, isIssueUrl, isPrUrl });
|
|
79
|
+
if (!text) {
|
|
80
|
+
argv.workLanguage = argv.workLanguage || 'en';
|
|
81
|
+
await log('Auto language detection could not fetch target text; using English work language.', { verbose: true });
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = detectIssueLanguageFromText(text);
|
|
86
|
+
argv.workLanguage = result.language;
|
|
87
|
+
await log(`Auto language detection selected work language: ${result.language} (en=${result.counts.en}, ru=${result.counts.ru}, detected=${result.counts.detected})`, { verbose: true });
|
|
88
|
+
return result;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
argv.workLanguage = argv.workLanguage || 'en';
|
|
91
|
+
await log(`Auto language detection failed: ${error?.message || error}; using English work language.`, { verbose: true });
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { t } from './i18n.lib.mjs';
|
|
2
|
+
|
|
3
|
+
const ENGLISH_LIMITS = {
|
|
4
|
+
additional_codex_limits: 'Additional Codex limits',
|
|
5
|
+
balance: 'balance',
|
|
6
|
+
claude_5_hour_session: 'Claude 5 hour session',
|
|
7
|
+
claude_limits: 'Claude limits',
|
|
8
|
+
codex_5_hour_session: 'Codex 5 hour session',
|
|
9
|
+
codex_credits: 'Codex credits',
|
|
10
|
+
codex_limits: 'Codex limits',
|
|
11
|
+
cpu: 'CPU',
|
|
12
|
+
cpu_cores_used: 'CPU cores used',
|
|
13
|
+
current_time: 'Current time',
|
|
14
|
+
current_week_all_models: 'Current week (all models)',
|
|
15
|
+
current_week_sonnet_only: 'Current week (Sonnet only)',
|
|
16
|
+
disabled_by_admin: '`--show-limits` is disabled by the bot administrator.',
|
|
17
|
+
disk_space: 'Disk space',
|
|
18
|
+
end: 'End',
|
|
19
|
+
five_hour_session: '5h session',
|
|
20
|
+
five_min_load_avg: '5m load avg',
|
|
21
|
+
github_api: 'GitHub API',
|
|
22
|
+
limits_at_end: 'Limits at end',
|
|
23
|
+
limits_at_start: 'Limits at start',
|
|
24
|
+
limits_change: 'Limits change',
|
|
25
|
+
na: 'N/A',
|
|
26
|
+
note_delta_approx: 'Note: delta is approximate (parallel sessions share the same budget).',
|
|
27
|
+
passed: 'passed',
|
|
28
|
+
plan: 'Plan',
|
|
29
|
+
ram: 'RAM',
|
|
30
|
+
requests: 'requests',
|
|
31
|
+
resets_at: 'Resets {{time}}',
|
|
32
|
+
resets_in: 'Resets in {{duration}}',
|
|
33
|
+
seven_day_all_models: '7d all models',
|
|
34
|
+
seven_day_sonnet_only: '7d Sonnet only',
|
|
35
|
+
session: 'session',
|
|
36
|
+
start: 'Start',
|
|
37
|
+
unavailable: 'unavailable',
|
|
38
|
+
unlimited: 'unlimited',
|
|
39
|
+
used: 'used',
|
|
40
|
+
week: 'week',
|
|
41
|
+
weekly: 'Weekly',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function applyParams(text, params = {}) {
|
|
45
|
+
let out = text;
|
|
46
|
+
for (const [key, value] of Object.entries(params)) {
|
|
47
|
+
out = out.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveLimitLocale(options = {}) {
|
|
53
|
+
if (typeof options === 'string') return options;
|
|
54
|
+
return options?.locale || null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function lt(key, params = {}, options = {}) {
|
|
58
|
+
const fullKey = `limits.${key}`;
|
|
59
|
+
const locale = resolveLimitLocale(options);
|
|
60
|
+
const translated = t(fullKey, params, locale ? { locale } : {});
|
|
61
|
+
if (translated !== fullKey) return translated;
|
|
62
|
+
return applyParams(ENGLISH_LIMITS[key] || key, params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatLimitResetsIn(duration, resetTime, options = {}) {
|
|
66
|
+
return `${lt('resets_in', { duration }, options)} (${resetTime})`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function formatLimitResetsAt(resetTime, options = {}) {
|
|
70
|
+
return lt('resets_at', { time: resetTime }, options);
|
|
71
|
+
}
|
package/src/limits.lib.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import dayjs from 'dayjs';
|
|
|
13
13
|
import utc from 'dayjs/plugin/utc.js';
|
|
14
14
|
|
|
15
15
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry, execGhWithRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller. execGhWithRetry adds transient-network retry (#1756).
|
|
16
|
+
import { formatLimitResetsAt, formatLimitResetsIn, lt, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
16
17
|
// Initialize dayjs plugins
|
|
17
18
|
dayjs.extend(utc);
|
|
18
19
|
|
|
@@ -280,13 +281,14 @@ function formatBytes(bytes) {
|
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
/**
|
|
283
|
-
* Format two byte values into a combined "used/total UNIT used" format
|
|
284
284
|
* @param {number} usedBytes - Used size in bytes
|
|
285
285
|
* @param {number} totalBytes - Total size in bytes
|
|
286
|
+
* @param {Object|string} options - Optional locale options
|
|
286
287
|
* @returns {string} Formatted string (e.g., "2.8/11.7 GB used")
|
|
287
288
|
*/
|
|
288
|
-
function formatBytesRange(usedBytes, totalBytes) {
|
|
289
|
-
|
|
289
|
+
function formatBytesRange(usedBytes, totalBytes, options = {}) {
|
|
290
|
+
const usedLabel = lt('used', {}, options);
|
|
291
|
+
if (totalBytes === 0) return `0/0 B ${usedLabel}`;
|
|
290
292
|
const k = 1024;
|
|
291
293
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
292
294
|
// Determine unit based on total (larger value)
|
|
@@ -295,7 +297,7 @@ function formatBytesRange(usedBytes, totalBytes) {
|
|
|
295
297
|
const totalValue = totalBytes / Math.pow(k, i);
|
|
296
298
|
// Use 1 decimal place for GB and above, none for smaller units
|
|
297
299
|
const decimals = i >= 3 ? 1 : 0;
|
|
298
|
-
return `${usedValue.toFixed(decimals)}/${totalValue.toFixed(decimals)} ${sizes[i]}
|
|
300
|
+
return `${usedValue.toFixed(decimals)}/${totalValue.toFixed(decimals)} ${sizes[i]} ${usedLabel}`;
|
|
299
301
|
}
|
|
300
302
|
|
|
301
303
|
function formatRoundedNumber(value, decimals = 2) {
|
|
@@ -307,6 +309,10 @@ function getDisplayCpuCoresUsed(loadAvg5, cpuCount) {
|
|
|
307
309
|
return formatRoundedNumber(boundedLoad);
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
function hasLimitPercentage(window) {
|
|
313
|
+
return window?.percentage !== null && window?.percentage !== undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
310
316
|
/**
|
|
311
317
|
* Get GitHub API rate limits by calling gh api rate_limit
|
|
312
318
|
* Returns rate limit info for core, search, graphql, and other resources
|
|
@@ -1019,74 +1025,68 @@ export function calculateTimePassedPercentage(resetsAt, periodHours) {
|
|
|
1019
1025
|
* @param {Object} memory - Optional memory info from getMemoryInfo
|
|
1020
1026
|
* @param {string|null} claudeError - Optional error message to show in Claude sections (e.g., auth expired)
|
|
1021
1027
|
* @param {string[]} extraSections - Optional extra sections to append inside the code block (e.g. queue status)
|
|
1028
|
+
* @param {Object|string} options - Optional locale options
|
|
1022
1029
|
* @returns {string} Formatted message wrapped in a single code block
|
|
1023
1030
|
* @see https://github.com/link-assistant/hive-mind/issues/1242
|
|
1024
1031
|
*/
|
|
1025
|
-
export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = null, cpuLoad = null, memory = null, claudeError = null, extraSections = []) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1032
|
+
export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = null, cpuLoad = null, memory = null, claudeError = null, extraSections = [], options = {}) {
|
|
1033
|
+
if (!Array.isArray(extraSections) && extraSections && typeof extraSections === 'object') {
|
|
1034
|
+
options = extraSections;
|
|
1035
|
+
extraSections = [];
|
|
1036
|
+
}
|
|
1037
|
+
const locale = resolveLimitLocale(options);
|
|
1029
1038
|
const sections = [];
|
|
1030
1039
|
|
|
1031
|
-
|
|
1032
|
-
sections.push(`Current time: ${formatCurrentTime()}\n`);
|
|
1040
|
+
sections.push(`${lt('current_time', {}, { locale })}: ${formatCurrentTime()}\n`);
|
|
1033
1041
|
|
|
1034
|
-
// CPU load section (if provided)
|
|
1035
|
-
// Threshold: Blocks new commands when usage >= 65%
|
|
1036
1042
|
if (cpuLoad) {
|
|
1037
|
-
let section = '
|
|
1043
|
+
let section = `${lt('cpu', {}, { locale })}\n`;
|
|
1038
1044
|
const usedBar = getProgressBar(cpuLoad.usagePercentage, DISPLAY_THRESHOLDS.CPU);
|
|
1039
1045
|
// Show 'used' label when below threshold, warning emoji when at/above threshold
|
|
1040
1046
|
// See: https://github.com/link-assistant/hive-mind/issues/1267
|
|
1041
|
-
const suffix = cpuLoad.usagePercentage >= DISPLAY_THRESHOLDS.CPU ? ' ⚠️' : '
|
|
1047
|
+
const suffix = cpuLoad.usagePercentage >= DISPLAY_THRESHOLDS.CPU ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1042
1048
|
section += `${usedBar} ${cpuLoad.usagePercentage}%${suffix}\n`;
|
|
1043
1049
|
// Linux load average is demand, not bounded CPU time. Keep the cores-used
|
|
1044
1050
|
// display within CPU capacity and show raw load average only when saturated.
|
|
1045
1051
|
const usedCpuCores = cpuLoad.usedCpuCores ?? getDisplayCpuCoresUsed(cpuLoad.loadAvg5, cpuLoad.cpuCount);
|
|
1046
|
-
let cpuCoresLine = `${formatRoundedNumber(usedCpuCores)}/${cpuLoad.cpuCount}
|
|
1052
|
+
let cpuCoresLine = `${formatRoundedNumber(usedCpuCores)}/${cpuLoad.cpuCount} ${lt('cpu_cores_used', {}, { locale })}`;
|
|
1047
1053
|
if (cpuLoad.loadAvg5 > cpuLoad.cpuCount) {
|
|
1048
|
-
cpuCoresLine += ` (
|
|
1054
|
+
cpuCoresLine += ` (${lt('five_min_load_avg', {}, { locale })} ${formatRoundedNumber(cpuLoad.loadAvg5)})`;
|
|
1049
1055
|
}
|
|
1050
1056
|
section += `${cpuCoresLine}\n`;
|
|
1051
1057
|
sections.push(section);
|
|
1052
1058
|
}
|
|
1053
1059
|
|
|
1054
|
-
// Memory section (if provided)
|
|
1055
|
-
// Threshold: Blocks new commands when usage >= 65%
|
|
1056
1060
|
if (memory) {
|
|
1057
|
-
let section = '
|
|
1061
|
+
let section = `${lt('ram', {}, { locale })}\n`;
|
|
1058
1062
|
const usedBar = getProgressBar(memory.usedPercentage, DISPLAY_THRESHOLDS.RAM);
|
|
1059
|
-
const suffix = memory.usedPercentage >= DISPLAY_THRESHOLDS.RAM ? ' ⚠️' : '
|
|
1063
|
+
const suffix = memory.usedPercentage >= DISPLAY_THRESHOLDS.RAM ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1060
1064
|
section += `${usedBar} ${memory.usedPercentage}%${suffix}\n`;
|
|
1061
|
-
section += `${formatBytesRange(memory.usedBytes, memory.totalBytes)}\n`;
|
|
1065
|
+
section += `${formatBytesRange(memory.usedBytes, memory.totalBytes, { locale })}\n`;
|
|
1062
1066
|
sections.push(section);
|
|
1063
1067
|
}
|
|
1064
1068
|
|
|
1065
|
-
// Disk space section (if provided)
|
|
1066
|
-
// Threshold: One-at-a-time mode when usage >= 90%
|
|
1067
1069
|
if (diskSpace) {
|
|
1068
|
-
let section = '
|
|
1069
|
-
// Show used percentage with progress bar and threshold marker
|
|
1070
|
+
let section = `${lt('disk_space', {}, { locale })}\n`;
|
|
1070
1071
|
const usedBar = getProgressBar(diskSpace.usedPercentage, DISPLAY_THRESHOLDS.DISK);
|
|
1071
|
-
const suffix = diskSpace.usedPercentage >= DISPLAY_THRESHOLDS.DISK ? ' ⚠️' : '
|
|
1072
|
+
const suffix = diskSpace.usedPercentage >= DISPLAY_THRESHOLDS.DISK ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1072
1073
|
section += `${usedBar} ${diskSpace.usedPercentage}%${suffix}\n`;
|
|
1073
|
-
section += `${formatBytesRange(diskSpace.usedBytes, diskSpace.totalBytes)}\n`;
|
|
1074
|
+
section += `${formatBytesRange(diskSpace.usedBytes, diskSpace.totalBytes, { locale })}\n`;
|
|
1074
1075
|
sections.push(section);
|
|
1075
1076
|
}
|
|
1076
1077
|
|
|
1077
1078
|
// GitHub API rate limits section (if provided)
|
|
1078
1079
|
// Threshold: Blocks parallel claude commands when >= 75%
|
|
1079
1080
|
if (githubRateLimit) {
|
|
1080
|
-
let section = '
|
|
1081
|
-
// Show used percentage with progress bar and threshold marker
|
|
1081
|
+
let section = `${lt('github_api', {}, { locale })}\n`;
|
|
1082
1082
|
const usedBar = getProgressBar(githubRateLimit.usedPercentage, DISPLAY_THRESHOLDS.GITHUB_API);
|
|
1083
|
-
const suffix = githubRateLimit.usedPercentage >= DISPLAY_THRESHOLDS.GITHUB_API ? ' ⚠️' : '
|
|
1083
|
+
const suffix = githubRateLimit.usedPercentage >= DISPLAY_THRESHOLDS.GITHUB_API ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1084
1084
|
section += `${usedBar} ${githubRateLimit.usedPercentage}%${suffix}\n`;
|
|
1085
|
-
section += `${githubRateLimit.used}/${githubRateLimit.limit} requests\n`;
|
|
1085
|
+
section += `${githubRateLimit.used}/${githubRateLimit.limit} ${lt('requests', {}, { locale })}\n`;
|
|
1086
1086
|
if (githubRateLimit.relativeReset) {
|
|
1087
|
-
section +=
|
|
1087
|
+
section += `${formatLimitResetsIn(githubRateLimit.relativeReset, githubRateLimit.resetTime, { locale })}\n`;
|
|
1088
1088
|
} else if (githubRateLimit.resetTime) {
|
|
1089
|
-
section +=
|
|
1089
|
+
section += `${formatLimitResetsAt(githubRateLimit.resetTime, { locale })}\n`;
|
|
1090
1090
|
}
|
|
1091
1091
|
sections.push(section);
|
|
1092
1092
|
}
|
|
@@ -1094,81 +1094,77 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1094
1094
|
// Claude limits section
|
|
1095
1095
|
// When there's an error (e.g., auth expired), show it once and skip empty subsections
|
|
1096
1096
|
if (claudeError) {
|
|
1097
|
-
sections.push(
|
|
1097
|
+
sections.push(`${lt('claude_limits', {}, { locale })}\n${claudeError}\n`);
|
|
1098
1098
|
} else {
|
|
1099
1099
|
// Claude 5 hour session (five_hour)
|
|
1100
1100
|
// Threshold: One-at-a-time mode when usage >= 65%
|
|
1101
|
-
let sessionSection = '
|
|
1102
|
-
if (usage
|
|
1103
|
-
// Add time passed progress bar first (no threshold marker for time)
|
|
1101
|
+
let sessionSection = `${lt('claude_5_hour_session', {}, { locale })}\n`;
|
|
1102
|
+
if (hasLimitPercentage(usage?.currentSession)) {
|
|
1104
1103
|
const timePassed = calculateTimePassedPercentage(usage.currentSession.resetsAt, 5);
|
|
1105
1104
|
if (timePassed !== null) {
|
|
1106
1105
|
const timeBar = getProgressBar(timePassed);
|
|
1107
|
-
sessionSection += `${timeBar} ${timePassed}% passed\n`;
|
|
1106
|
+
sessionSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1108
1107
|
}
|
|
1109
1108
|
|
|
1110
|
-
// Add usage progress bar second with threshold marker
|
|
1111
1109
|
// Use Math.floor so 100% only appears when usage is exactly 100%
|
|
1112
1110
|
// See: https://github.com/link-assistant/hive-mind/issues/1133
|
|
1113
1111
|
const pct = Math.floor(usage.currentSession.percentage);
|
|
1114
1112
|
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION);
|
|
1115
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : '
|
|
1113
|
+
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1116
1114
|
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1117
1115
|
|
|
1118
1116
|
if (usage.currentSession.resetTime) {
|
|
1119
1117
|
const relativeTime = formatRelativeTime(usage.currentSession.resetsAt);
|
|
1120
1118
|
if (relativeTime) {
|
|
1121
|
-
sessionSection +=
|
|
1119
|
+
sessionSection += `${formatLimitResetsIn(relativeTime, usage.currentSession.resetTime, { locale })}\n`;
|
|
1122
1120
|
} else {
|
|
1123
|
-
sessionSection +=
|
|
1121
|
+
sessionSection += `${formatLimitResetsAt(usage.currentSession.resetTime, { locale })}\n`;
|
|
1124
1122
|
}
|
|
1125
1123
|
}
|
|
1126
1124
|
} else {
|
|
1127
|
-
sessionSection += '
|
|
1125
|
+
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1128
1126
|
}
|
|
1129
1127
|
sections.push(sessionSection);
|
|
1130
1128
|
|
|
1131
1129
|
// Current week (all models / seven_day)
|
|
1132
1130
|
// Threshold: One-at-a-time mode when usage >= 97%
|
|
1133
|
-
let allModelsSection = '
|
|
1134
|
-
if (usage
|
|
1135
|
-
// Add time passed progress bar first (no threshold marker for time)
|
|
1131
|
+
let allModelsSection = `${lt('current_week_all_models', {}, { locale })}\n`;
|
|
1132
|
+
if (hasLimitPercentage(usage?.allModels)) {
|
|
1136
1133
|
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1137
1134
|
if (timePassed !== null) {
|
|
1138
1135
|
const timeBar = getProgressBar(timePassed);
|
|
1139
|
-
allModelsSection += `${timeBar} ${timePassed}% passed\n`;
|
|
1136
|
+
allModelsSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1140
1137
|
}
|
|
1141
1138
|
|
|
1142
|
-
// Add usage progress bar second with threshold marker
|
|
1143
1139
|
// Use Math.floor so 100% only appears when usage is exactly 100%
|
|
1144
1140
|
// See: https://github.com/link-assistant/hive-mind/issues/1133
|
|
1145
1141
|
const pct = Math.floor(usage.allModels.percentage);
|
|
1146
1142
|
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
|
|
1147
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : '
|
|
1143
|
+
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1148
1144
|
allModelsSection += `${bar} ${pct}%${suffix}\n`;
|
|
1149
1145
|
|
|
1150
1146
|
if (usage.allModels.resetTime) {
|
|
1151
1147
|
const relativeTime = formatRelativeTime(usage.allModels.resetsAt);
|
|
1152
1148
|
if (relativeTime) {
|
|
1153
|
-
allModelsSection +=
|
|
1149
|
+
allModelsSection += `${formatLimitResetsIn(relativeTime, usage.allModels.resetTime, { locale })}\n`;
|
|
1154
1150
|
} else {
|
|
1155
|
-
allModelsSection +=
|
|
1151
|
+
allModelsSection += `${formatLimitResetsAt(usage.allModels.resetTime, { locale })}\n`;
|
|
1156
1152
|
}
|
|
1157
1153
|
}
|
|
1158
1154
|
} else {
|
|
1159
|
-
allModelsSection += '
|
|
1155
|
+
allModelsSection += `${lt('na', {}, { locale })}\n`;
|
|
1160
1156
|
}
|
|
1161
1157
|
sections.push(allModelsSection);
|
|
1162
1158
|
|
|
1163
1159
|
// Current week (Sonnet only / seven_day_sonnet)
|
|
1164
1160
|
// Threshold: One-at-a-time mode when usage >= 97% (same as all models)
|
|
1165
|
-
let sonnetSection = '
|
|
1166
|
-
if (usage
|
|
1161
|
+
let sonnetSection = `${lt('current_week_sonnet_only', {}, { locale })}\n`;
|
|
1162
|
+
if (hasLimitPercentage(usage?.sonnetOnly)) {
|
|
1167
1163
|
// Add time passed progress bar first (no threshold marker for time)
|
|
1168
1164
|
const timePassed = calculateTimePassedPercentage(usage.sonnetOnly.resetsAt, 168);
|
|
1169
1165
|
if (timePassed !== null) {
|
|
1170
1166
|
const timeBar = getProgressBar(timePassed);
|
|
1171
|
-
sonnetSection += `${timeBar} ${timePassed}% passed\n`;
|
|
1167
|
+
sonnetSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1172
1168
|
}
|
|
1173
1169
|
|
|
1174
1170
|
// Add usage progress bar second with threshold marker
|
|
@@ -1176,19 +1172,19 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1176
1172
|
// See: https://github.com/link-assistant/hive-mind/issues/1133
|
|
1177
1173
|
const pct = Math.floor(usage.sonnetOnly.percentage);
|
|
1178
1174
|
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
|
|
1179
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : '
|
|
1175
|
+
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1180
1176
|
sonnetSection += `${bar} ${pct}%${suffix}\n`;
|
|
1181
1177
|
|
|
1182
1178
|
if (usage.sonnetOnly.resetTime) {
|
|
1183
1179
|
const relativeTime = formatRelativeTime(usage.sonnetOnly.resetsAt);
|
|
1184
1180
|
if (relativeTime) {
|
|
1185
|
-
sonnetSection +=
|
|
1181
|
+
sonnetSection += `${formatLimitResetsIn(relativeTime, usage.sonnetOnly.resetTime, { locale })}\n`;
|
|
1186
1182
|
} else {
|
|
1187
|
-
sonnetSection +=
|
|
1183
|
+
sonnetSection += `${formatLimitResetsAt(usage.sonnetOnly.resetTime, { locale })}\n`;
|
|
1188
1184
|
}
|
|
1189
1185
|
}
|
|
1190
1186
|
} else {
|
|
1191
|
-
sonnetSection += '
|
|
1187
|
+
sonnetSection += `${lt('na', {}, { locale })}\n`;
|
|
1192
1188
|
}
|
|
1193
1189
|
sections.push(sonnetSection);
|
|
1194
1190
|
}
|
|
@@ -1208,11 +1204,13 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1208
1204
|
*
|
|
1209
1205
|
* @param {Object|null} codexLimits - Result object from getCodexUsageLimits, or null
|
|
1210
1206
|
* @param {string|null} codexError - Optional error message
|
|
1207
|
+
* @param {Object|string} options - Optional locale options
|
|
1211
1208
|
* @returns {string} Formatted section text
|
|
1212
1209
|
*/
|
|
1213
|
-
export function formatCodexLimitsSection(codexLimits, codexError = null) {
|
|
1210
|
+
export function formatCodexLimitsSection(codexLimits, codexError = null, options = {}) {
|
|
1211
|
+
const locale = resolveLimitLocale(options);
|
|
1214
1212
|
if (codexError) {
|
|
1215
|
-
return
|
|
1213
|
+
return `${lt('codex_limits', {}, { locale })}\n${codexError}\n`;
|
|
1216
1214
|
}
|
|
1217
1215
|
|
|
1218
1216
|
const usage = codexLimits?.usage || null;
|
|
@@ -1220,63 +1218,63 @@ export function formatCodexLimitsSection(codexLimits, codexError = null) {
|
|
|
1220
1218
|
const credits = codexLimits?.credits || null;
|
|
1221
1219
|
const planType = codexLimits?.planType || null;
|
|
1222
1220
|
|
|
1223
|
-
let section = '
|
|
1221
|
+
let section = `${lt('codex_limits', {}, { locale })}\n`;
|
|
1224
1222
|
if (planType) {
|
|
1225
|
-
section +=
|
|
1223
|
+
section += `${lt('plan', {}, { locale })}: ${planType}\n`;
|
|
1226
1224
|
}
|
|
1227
1225
|
|
|
1228
|
-
let sessionSection = '
|
|
1229
|
-
if (usage?.currentSession
|
|
1226
|
+
let sessionSection = `${lt('codex_5_hour_session', {}, { locale })}\n`;
|
|
1227
|
+
if (hasLimitPercentage(usage?.currentSession)) {
|
|
1230
1228
|
const timePassed = calculateTimePassedPercentage(usage.currentSession.resetsAt, 5);
|
|
1231
1229
|
if (timePassed !== null) {
|
|
1232
|
-
sessionSection += `${getProgressBar(timePassed)} ${timePassed}% passed\n`;
|
|
1230
|
+
sessionSection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1233
1231
|
}
|
|
1234
1232
|
const pct = Math.floor(usage.currentSession.percentage);
|
|
1235
1233
|
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION);
|
|
1236
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION ? ' ⚠️' : '
|
|
1234
|
+
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1237
1235
|
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1238
1236
|
if (usage.currentSession.resetTime) {
|
|
1239
1237
|
const relativeTime = formatRelativeTime(usage.currentSession.resetsAt);
|
|
1240
|
-
sessionSection += relativeTime ?
|
|
1238
|
+
sessionSection += relativeTime ? `${formatLimitResetsIn(relativeTime, usage.currentSession.resetTime, { locale })}\n` : `${formatLimitResetsAt(usage.currentSession.resetTime, { locale })}\n`;
|
|
1241
1239
|
}
|
|
1242
1240
|
} else {
|
|
1243
|
-
sessionSection += '
|
|
1241
|
+
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1244
1242
|
}
|
|
1245
1243
|
|
|
1246
|
-
let weeklySection = '
|
|
1247
|
-
if (usage?.allModels
|
|
1244
|
+
let weeklySection = `${lt('current_week_all_models', {}, { locale })}\n`;
|
|
1245
|
+
if (hasLimitPercentage(usage?.allModels)) {
|
|
1248
1246
|
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1249
1247
|
if (timePassed !== null) {
|
|
1250
|
-
weeklySection += `${getProgressBar(timePassed)} ${timePassed}% passed\n`;
|
|
1248
|
+
weeklySection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1251
1249
|
}
|
|
1252
1250
|
const pct = Math.floor(usage.allModels.percentage);
|
|
1253
1251
|
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_WEEKLY);
|
|
1254
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_WEEKLY ? ' ⚠️' : '
|
|
1252
|
+
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1255
1253
|
weeklySection += `${bar} ${pct}%${suffix}\n`;
|
|
1256
1254
|
if (usage.allModels.resetTime) {
|
|
1257
1255
|
const relativeTime = formatRelativeTime(usage.allModels.resetsAt);
|
|
1258
|
-
weeklySection += relativeTime ?
|
|
1256
|
+
weeklySection += relativeTime ? `${formatLimitResetsIn(relativeTime, usage.allModels.resetTime, { locale })}\n` : `${formatLimitResetsAt(usage.allModels.resetTime, { locale })}\n`;
|
|
1259
1257
|
}
|
|
1260
1258
|
} else {
|
|
1261
|
-
weeklySection += '
|
|
1259
|
+
weeklySection += `${lt('na', {}, { locale })}\n`;
|
|
1262
1260
|
}
|
|
1263
1261
|
|
|
1264
1262
|
section += `${sessionSection}\n${weeklySection}`;
|
|
1265
1263
|
|
|
1266
1264
|
if (additionalRateLimits.length > 0) {
|
|
1267
|
-
section += '
|
|
1265
|
+
section += `\n${lt('additional_codex_limits', {}, { locale })}\n`;
|
|
1268
1266
|
for (const limit of additionalRateLimits) {
|
|
1269
1267
|
const sessionPct = limit.currentSession?.percentage;
|
|
1270
1268
|
const weeklyPct = limit.allModels?.percentage;
|
|
1271
|
-
const sessionText = sessionPct === null ? 'session
|
|
1272
|
-
const weeklyText = weeklyPct === null ? 'week
|
|
1269
|
+
const sessionText = sessionPct === null || sessionPct === undefined ? `${lt('session', {}, { locale })} ${lt('na', {}, { locale })}` : `${lt('session', {}, { locale })} ${Math.floor(sessionPct)}%`;
|
|
1270
|
+
const weeklyText = weeklyPct === null || weeklyPct === undefined ? `${lt('week', {}, { locale })} ${lt('na', {}, { locale })}` : `${lt('week', {}, { locale })} ${Math.floor(weeklyPct)}%`;
|
|
1273
1271
|
section += `${limit.limitName}: ${sessionText}, ${weeklyText}\n`;
|
|
1274
1272
|
}
|
|
1275
1273
|
}
|
|
1276
1274
|
|
|
1277
1275
|
if (credits) {
|
|
1278
|
-
const creditSummary = credits.unlimited ? 'unlimited' : `${credits.balance ?? '0'} balance`;
|
|
1279
|
-
section += `\
|
|
1276
|
+
const creditSummary = credits.unlimited ? lt('unlimited', {}, { locale }) : `${credits.balance ?? '0'} ${lt('balance', {}, { locale })}`;
|
|
1277
|
+
section += `\n${lt('codex_credits', {}, { locale })}\n${creditSummary}\n`;
|
|
1280
1278
|
}
|
|
1281
1279
|
|
|
1282
1280
|
return section;
|