@link-assistant/hive-mind 1.56.8 → 1.56.10
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 +1 -1
- package/src/hive.mjs +4 -2
- package/src/memory-check.mjs +2 -1
- package/src/session-monitor.lib.mjs +14 -16
- package/src/solve.config.lib.mjs +2 -1
- package/src/telegram-bot.mjs +14 -7
- package/src/telegram-solve-queue.lib.mjs +7 -2
- package/src/work-session-formatting.lib.mjs +72 -0
- package/src/yargs-factory.lib.mjs +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.56.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e2f9a37: Fix duplicated yargs choice values in Telegram validation errors.
|
|
8
|
+
|
|
9
|
+
## 1.56.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 94448c3: Fix screen-isolated work-session Telegram updates so executing messages stay compact and completion messages use `$ --status` start/end timestamps and exit codes.
|
|
14
|
+
|
|
3
15
|
## 1.56.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/hive.mjs
CHANGED
|
@@ -19,7 +19,8 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
|
19
19
|
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
|
|
20
20
|
globalThis.use = use;
|
|
21
21
|
const yargsModule = await use('yargs@17.7.2');
|
|
22
|
-
const
|
|
22
|
+
const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
|
|
23
|
+
const yargs = resolveYargsFactory(yargsModule);
|
|
23
24
|
const helpersModuleHelp = await use('yargs@17.7.2/helpers');
|
|
24
25
|
const _helpersHelp = helpersModuleHelp.default || helpersModuleHelp;
|
|
25
26
|
const hideBinHelp = _helpersHelp.hideBin || (argv => argv.slice(2));
|
|
@@ -71,7 +72,8 @@ if (isRunningDirectly) {
|
|
|
71
72
|
'loading command-stream'
|
|
72
73
|
);
|
|
73
74
|
const yargsModule = await withTimeout(use('yargs@17.7.2'), 30000, 'loading yargs');
|
|
74
|
-
const
|
|
75
|
+
const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
|
|
76
|
+
const yargs = resolveYargsFactory(yargsModule);
|
|
75
77
|
const helpersModuleMain = await withTimeout(use('yargs@17.7.2/helpers'), 30000, 'loading yargs helpers');
|
|
76
78
|
const _helpersMain = helpersModuleMain.default || helpersModuleMain;
|
|
77
79
|
const hideBin = _helpersMain.hideBin || (argv => argv.slice(2));
|
package/src/memory-check.mjs
CHANGED
|
@@ -6,6 +6,7 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
6
6
|
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
7
7
|
}
|
|
8
8
|
const use = globalThis.use;
|
|
9
|
+
const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
|
|
9
10
|
|
|
10
11
|
// Temporarily unset CI to avoid command-stream trace logs
|
|
11
12
|
const originalCI = process.env.CI;
|
|
@@ -18,7 +19,7 @@ const { $ } = await use('command-stream');
|
|
|
18
19
|
const $silent = $({ mirror: false, capture: true });
|
|
19
20
|
|
|
20
21
|
const yargsModule = await use('yargs@17.7.2');
|
|
21
|
-
const yargs = yargsModule
|
|
22
|
+
const yargs = resolveYargsFactory(yargsModule);
|
|
22
23
|
const helpersModule = await use('yargs@17.7.2/helpers');
|
|
23
24
|
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
24
25
|
const _helpers = helpersModule.default || helpersModule;
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import { promisify } from 'util';
|
|
19
19
|
import { exec as execCallback } from 'child_process';
|
|
20
|
+
import { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
|
|
21
|
+
|
|
22
|
+
export { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
|
|
20
23
|
|
|
21
24
|
const exec = promisify(execCallback);
|
|
22
25
|
|
|
@@ -201,6 +204,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
201
204
|
for (const { sessionName, sessionInfo } of sessions) {
|
|
202
205
|
let stillRunning;
|
|
203
206
|
let exitCode = null;
|
|
207
|
+
let statusResult = null;
|
|
204
208
|
|
|
205
209
|
if (sessionInfo.isolationBackend && sessionInfo.sessionId) {
|
|
206
210
|
// Isolation mode: use $ --status, with screen -ls only as a fallback
|
|
@@ -209,6 +213,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
209
213
|
const state = await getIsolationSessionState(sessionName, sessionInfo, { verbose });
|
|
210
214
|
stillRunning = state.running;
|
|
211
215
|
exitCode = state.exitCode;
|
|
216
|
+
statusResult = state.statusResult;
|
|
212
217
|
} else {
|
|
213
218
|
// Issue #1586: Non-isolation screen sessions cannot reliably detect
|
|
214
219
|
// completion because start-screen keeps the screen alive via `exec bash`.
|
|
@@ -233,21 +238,14 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
233
238
|
console.log(`Session ${sessionName} has finished. Sending notification to chat ${sessionInfo.chatId}`);
|
|
234
239
|
|
|
235
240
|
try {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const isolationInfo = sessionInfo.isolationBackend ? `\n🔒 Isolation: ${sessionInfo.isolationBackend}` : '';
|
|
245
|
-
|
|
246
|
-
let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
|
|
247
|
-
message += `📊 Session: \`${sessionName}\`\n`;
|
|
248
|
-
message += `⏱️ Duration: ${minutes}m ${seconds}s\n`;
|
|
249
|
-
message += `🔗 URL: ${sessionInfo.url}${isolationInfo}\n\n`;
|
|
250
|
-
message += `The work session has finished. You can now review the results.`;
|
|
241
|
+
const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
|
|
242
|
+
const message = formatSessionCompletionMessage({
|
|
243
|
+
sessionName,
|
|
244
|
+
sessionInfo,
|
|
245
|
+
statusResult,
|
|
246
|
+
observedEndTime: new Date(),
|
|
247
|
+
exitCode: finalExitCode,
|
|
248
|
+
});
|
|
251
249
|
|
|
252
250
|
// Update the original reply message if messageId is available, otherwise send new message
|
|
253
251
|
if (sessionInfo.messageId) {
|
|
@@ -256,7 +254,7 @@ export async function monitorSessions(bot, verbose = false) {
|
|
|
256
254
|
await bot.telegram.sendMessage(sessionInfo.chatId, message, { parse_mode: 'Markdown' });
|
|
257
255
|
}
|
|
258
256
|
|
|
259
|
-
completeSession(sessionName,
|
|
257
|
+
completeSession(sessionName, finalExitCode || 0, verbose);
|
|
260
258
|
} catch (error) {
|
|
261
259
|
console.error(`Failed to send completion notification for ${sessionName}:`, error);
|
|
262
260
|
completeSession(sessionName, 1, verbose);
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { enhanceErrorMessage, detectMalformedFlags } from './option-suggestions.lib.mjs';
|
|
11
11
|
import { defaultModels, buildModelOptionDescription, resolveDefaultFallbackModel, resolveRuntimeDefaultModel } from './models/index.mjs';
|
|
12
12
|
import { validateBranchName } from './solve.branch.lib.mjs';
|
|
13
|
+
import { resolveYargsFactory } from './yargs-factory.lib.mjs';
|
|
13
14
|
|
|
14
15
|
// Re-export for use by telegram-bot.mjs (avoids extra import lines there)
|
|
15
16
|
export { detectMalformedFlags };
|
|
@@ -18,7 +19,7 @@ export { detectMalformedFlags };
|
|
|
18
19
|
export const initializeConfig = async use => {
|
|
19
20
|
// Import yargs with specific version for hideBin support
|
|
20
21
|
const yargsModule = await use('yargs@17.7.2');
|
|
21
|
-
const yargs = yargsModule
|
|
22
|
+
const yargs = resolveYargsFactory(yargsModule);
|
|
22
23
|
const helpersModule = await use('yargs@17.7.2/helpers');
|
|
23
24
|
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
24
25
|
const helpers = helpersModule.default || helpersModule;
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -25,13 +25,14 @@ const dotenvxModule = await use('@dotenvx/dotenvx');
|
|
|
25
25
|
const dotenvx = dotenvxModule.default || dotenvxModule;
|
|
26
26
|
const getenvModule = await use('getenv');
|
|
27
27
|
const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.default || getenvModule;
|
|
28
|
+
const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
|
|
28
29
|
|
|
29
30
|
// Load .env/.lenv configuration (issue #1318)
|
|
30
31
|
dotenvx.config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
|
|
31
32
|
await loadLenvConfig({ override: true, quiet: true });
|
|
32
33
|
|
|
33
34
|
const yargsModule = await use('yargs@17.7.2');
|
|
34
|
-
const yargs = yargsModule
|
|
35
|
+
const yargs = resolveYargsFactory(yargsModule);
|
|
35
36
|
const helpersModuleBot = await use('yargs@17.7.2/helpers');
|
|
36
37
|
const _helpersBot = helpersModuleBot.default || helpersModuleBot;
|
|
37
38
|
const hideBin = _helpersBot.hideBin || (argv => argv.slice(2));
|
|
@@ -50,6 +51,7 @@ const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STO
|
|
|
50
51
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
51
52
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
52
53
|
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
54
|
+
const { formatExecutingWorkSessionMessage } = await import('./work-session-formatting.lib.mjs');
|
|
53
55
|
|
|
54
56
|
const config = yargs(hideBin(process.argv))
|
|
55
57
|
.usage('Usage: hive-telegram-bot [options]')
|
|
@@ -559,14 +561,11 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
559
561
|
}
|
|
560
562
|
};
|
|
561
563
|
const iso = await resolveIsolation(perCommandIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE);
|
|
562
|
-
let result,
|
|
563
|
-
session,
|
|
564
|
-
extraInfo = '';
|
|
564
|
+
let result, session;
|
|
565
565
|
if (iso) {
|
|
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
|
-
extraInfo = `\n🔒 Isolation: \`${iso.backend}\``;
|
|
570
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);
|
|
571
570
|
} else {
|
|
572
571
|
result = await executeStartScreen(commandName, args);
|
|
@@ -579,8 +578,16 @@ async function executeAndUpdateMessage(ctx, startingMessage, commandName, args,
|
|
|
579
578
|
if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, tool }, VERBOSE);
|
|
580
579
|
}
|
|
581
580
|
if (result.warning) return safeEdit(`⚠️ ${result.warning}`);
|
|
582
|
-
if (result.success)
|
|
583
|
-
|
|
581
|
+
if (result.success) {
|
|
582
|
+
await safeEdit(
|
|
583
|
+
formatExecutingWorkSessionMessage({
|
|
584
|
+
commandName,
|
|
585
|
+
sessionName: session,
|
|
586
|
+
isolationBackend: iso?.backend || null,
|
|
587
|
+
infoBlock,
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
} else await safeEdit(`❌ Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
|
|
584
591
|
}
|
|
585
592
|
|
|
586
593
|
bot.command('help', async ctx => {
|
|
@@ -20,6 +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
24
|
|
|
24
25
|
export const QueueItemStatus = {
|
|
25
26
|
QUEUED: 'queued',
|
|
@@ -1145,8 +1146,12 @@ export class SolveQueue {
|
|
|
1145
1146
|
if (result.warning) {
|
|
1146
1147
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, `⚠️ ${result.warning}`, { parse_mode: 'Markdown' });
|
|
1147
1148
|
} else if (result.success) {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1149
|
+
const response = formatExecutingWorkSessionMessage({
|
|
1150
|
+
commandName: item.command || 'solve',
|
|
1151
|
+
sessionName,
|
|
1152
|
+
isolationBackend: result.isolationBackend,
|
|
1153
|
+
infoBlock: item.infoBlock,
|
|
1154
|
+
});
|
|
1150
1155
|
await item.ctx.telegram.editMessageText(chatId, messageId, undefined, response, { parse_mode: 'Markdown' });
|
|
1151
1156
|
} else {
|
|
1152
1157
|
const response = `❌ Error executing solve command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const FAILURE_STATUSES = new Set(['failed', 'cancelled', 'canceled', 'error']);
|
|
2
|
+
|
|
3
|
+
function capitalizeCommandName(commandName) {
|
|
4
|
+
const normalized = commandName || 'solve';
|
|
5
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parseDateValue(value) {
|
|
9
|
+
if (!value) return null;
|
|
10
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
11
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeExitCode(value) {
|
|
15
|
+
if (value === null || value === undefined) return null;
|
|
16
|
+
const numeric = Number(value);
|
|
17
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getSessionCompletionExitCode({ exitCode = null, statusResult = null } = {}) {
|
|
21
|
+
const explicitExitCode = normalizeExitCode(exitCode);
|
|
22
|
+
if (explicitExitCode !== null) return explicitExitCode;
|
|
23
|
+
|
|
24
|
+
const statusExitCode = normalizeExitCode(statusResult?.exitCode);
|
|
25
|
+
if (statusExitCode !== null) return statusExitCode;
|
|
26
|
+
|
|
27
|
+
const status = String(statusResult?.status || '').toLowerCase();
|
|
28
|
+
if (FAILURE_STATUSES.has(status)) return 1;
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatSessionDurationSeconds(seconds) {
|
|
34
|
+
const totalSeconds = Math.max(0, Math.round(Number(seconds) || 0));
|
|
35
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
36
|
+
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
37
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
38
|
+
const remainingSeconds = totalSeconds % 60;
|
|
39
|
+
const parts = [];
|
|
40
|
+
|
|
41
|
+
if (days > 0) parts.push(`${days}d`);
|
|
42
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
43
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
44
|
+
if (remainingSeconds > 0 || parts.length === 0) parts.push(`${remainingSeconds}s`);
|
|
45
|
+
|
|
46
|
+
return parts.join(' ');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatExecutingWorkSessionMessage({ commandName = 'solve', sessionName = 'unknown', isolationBackend = null, infoBlock = '' } = {}) {
|
|
50
|
+
const isolationInfo = isolationBackend ? `\n🔒 Isolation: \`${isolationBackend}\`` : '';
|
|
51
|
+
const details = infoBlock ? `\n\n${infoBlock}` : '';
|
|
52
|
+
return `⏳ ${capitalizeCommandName(commandName)} command executing...\n\n📊 Session: \`${sessionName}\`${isolationInfo}${details}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null } = {}) {
|
|
56
|
+
const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
|
|
57
|
+
const failed = finalExitCode !== null && finalExitCode !== 0;
|
|
58
|
+
const statusEmoji = failed ? '❌' : '✅';
|
|
59
|
+
const statusText = failed ? `Failed (exit code: ${finalExitCode})` : 'Completed';
|
|
60
|
+
const isolationInfo = sessionInfo?.isolationBackend ? `\n🔒 Isolation: ${sessionInfo.isolationBackend}` : '';
|
|
61
|
+
const startTime = parseDateValue(statusResult?.startTime) || parseDateValue(sessionInfo?.startTime) || observedEndTime;
|
|
62
|
+
const endTime = parseDateValue(statusResult?.endTime) || observedEndTime;
|
|
63
|
+
const durationSeconds = Math.max(0, (endTime.getTime() - startTime.getTime()) / 1000);
|
|
64
|
+
|
|
65
|
+
let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
|
|
66
|
+
message += `📊 Session: \`${sessionName || 'unknown'}\`\n`;
|
|
67
|
+
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.';
|
|
70
|
+
|
|
71
|
+
return message;
|
|
72
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Resolve use-m's yargs import to the fresh parser factory.
|
|
2
|
+
//
|
|
3
|
+
// In the use-m yargs@17.7.2 shape, the module object is the factory that
|
|
4
|
+
// creates independent parser instances, while module.default is a singleton
|
|
5
|
+
// wrapper. Reusing the singleton accumulates options and duplicates choice
|
|
6
|
+
// values in validation errors.
|
|
7
|
+
export function resolveYargsFactory(yargsModule) {
|
|
8
|
+
if (typeof yargsModule === 'function' && typeof yargsModule.getInternalMethods === 'function') {
|
|
9
|
+
return yargsModule;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const candidate = yargsModule?.default || yargsModule;
|
|
13
|
+
if (typeof candidate !== 'function') {
|
|
14
|
+
throw new TypeError('Unable to resolve yargs factory from imported module');
|
|
15
|
+
}
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|