@link-assistant/hive-mind 2.7.3 → 2.8.0
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 +40 -0
- package/README.hi.md +22 -0
- package/README.md +24 -0
- package/README.ru.md +21 -0
- package/README.zh.md +20 -0
- package/package.json +3 -2
- package/src/fix.ci-cd.lib.mjs +480 -0
- package/src/fix.mjs +242 -0
- package/src/github-merge-ci-wait.lib.mjs +217 -0
- package/src/github-merge-ci.lib.mjs +10 -3
- package/src/github-merge-repo-actions.lib.mjs +14 -3
- package/src/github-merge.lib.mjs +13 -195
- package/src/interruptible-sleep.lib.mjs +34 -1
- package/src/locales/en.lino +6 -1
- package/src/locales/hi.lino +6 -1
- package/src/locales/ru.lino +6 -1
- package/src/locales/zh.lino +6 -1
- package/src/task.issue-creation.lib.mjs +31 -5
- package/src/telegram-bot.mjs +10 -85
- package/src/telegram-fix-command.lib.mjs +164 -0
- package/src/telegram-merge-queue.lib.mjs +23 -20
- package/src/telegram-merge-wait.lib.mjs +7 -1
- package/src/telegram-ui-messages.lib.mjs +11 -1
- package/src/telegram.config.lib.mjs +96 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram `/fix` command (issue #1733).
|
|
3
|
+
*
|
|
4
|
+
* Spawns the `fix` CLI in a work session, exactly like `/solve` and `/task` do
|
|
5
|
+
* for their own CLIs. `fix` creates the CI/CD remediation issue and then hands
|
|
6
|
+
* it off to `/solve --development-log --deep-analysis --auto-merge` itself, so
|
|
7
|
+
* this handler only has to validate the request and start the session.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { buildUserMention } from './buildUserMention.lib.mjs';
|
|
11
|
+
import { validateModelName } from './models/index.mjs';
|
|
12
|
+
import { parseFixRepository } from './fix.ci-cd.lib.mjs';
|
|
13
|
+
import { escapeMarkdown } from './telegram-markdown.lib.mjs';
|
|
14
|
+
import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
|
|
15
|
+
import { moveArgumentToFront, parseCommandArgs } from './telegram-solve-command.lib.mjs';
|
|
16
|
+
import { formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
17
|
+
|
|
18
|
+
export const FIX_COMMAND_NAMES = Object.freeze(['fix']);
|
|
19
|
+
|
|
20
|
+
export function getFixCommandNameFromText(text) {
|
|
21
|
+
if (!text || typeof text !== 'string') return null;
|
|
22
|
+
const firstLine = text.split('\n')[0].trim();
|
|
23
|
+
const match = firstLine.match(/^\/(\w+)(?:@\S+)?(?:\s|$)/);
|
|
24
|
+
const command = match ? match[1].toLowerCase() : null;
|
|
25
|
+
return FIX_COMMAND_NAMES.includes(command) ? command : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `--ci-cd` is the only mode `fix` supports today and it is required by the
|
|
30
|
+
* CLI, so the chat command implies it instead of making every user type it.
|
|
31
|
+
*/
|
|
32
|
+
export function applyFixCommandDefaults(args) {
|
|
33
|
+
const hasCiCd = args.includes('--ci-cd');
|
|
34
|
+
return hasCiCd ? args : [...args, '--ci-cd'];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findFixRepositoryArg(args) {
|
|
38
|
+
return args.find(arg => !arg.startsWith('-') && parseFixRepository(arg)) || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getFixToolFromArgs(args) {
|
|
42
|
+
for (let i = 0; i < args.length; i++) {
|
|
43
|
+
if (args[i] === '--tool' && i + 1 < args.length) return args[i + 1];
|
|
44
|
+
if (args[i].startsWith('--tool=')) return args[i].substring('--tool='.length);
|
|
45
|
+
}
|
|
46
|
+
return 'claude';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getModelFromArgs(args) {
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
if ((args[i] === '--model' || args[i] === '-m') && i + 1 < args.length) return args[i + 1];
|
|
52
|
+
if (args[i].startsWith('--model=')) return args[i].substring('--model='.length);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateFixModel(args) {
|
|
58
|
+
const model = getModelFromArgs(args);
|
|
59
|
+
if (!model) return null;
|
|
60
|
+
const validation = validateModelName(model, getFixToolFromArgs(args));
|
|
61
|
+
return validation.valid ? null : validation.message;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function buildFixCommandArgs(text) {
|
|
65
|
+
const args = applyFixCommandDefaults(parseCommandArgs(text));
|
|
66
|
+
const repositoryRaw = findFixRepositoryArg(args);
|
|
67
|
+
const repository = repositoryRaw ? parseFixRepository(repositoryRaw) : null;
|
|
68
|
+
return {
|
|
69
|
+
// `fix` reads the repository from the first bare argument, so normalize the
|
|
70
|
+
// shorthand (`owner/repo`) to a full URL and move it to the front.
|
|
71
|
+
args: repository ? moveArgumentToFront(args, repository.url, value => parseFixRepository(value)?.url || value) : args,
|
|
72
|
+
repositoryRaw,
|
|
73
|
+
repository,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Issue #378: inject --language LOCALE into spawn args if no language flag is
|
|
78
|
+
// already present, so spawned fix sessions inherit the user's effective locale.
|
|
79
|
+
function injectLanguageIfMissing(args, locale) {
|
|
80
|
+
if (!locale || !args || !Array.isArray(args)) return args;
|
|
81
|
+
const langFlags = new Set(['--language', '--ui-language', '--work-language']);
|
|
82
|
+
for (const arg of args) {
|
|
83
|
+
const flag = arg.startsWith('--') ? arg.split('=')[0] : null;
|
|
84
|
+
if (flag && langFlags.has(flag)) return args;
|
|
85
|
+
}
|
|
86
|
+
return [...args, '--language', locale];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function registerFixCommand(bot, options) {
|
|
90
|
+
const { VERBOSE, fixEnabled, addBreadcrumb, isOldMessage, isForwardedOrReply, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, resolveLocale = null } = options;
|
|
91
|
+
|
|
92
|
+
async function handleFixCommand(ctx) {
|
|
93
|
+
const commandDisplay = '/fix';
|
|
94
|
+
VERBOSE && console.log(`[VERBOSE] ${commandDisplay} command received`);
|
|
95
|
+
|
|
96
|
+
await addBreadcrumb({
|
|
97
|
+
category: 'telegram.command',
|
|
98
|
+
message: `${commandDisplay} command received`,
|
|
99
|
+
level: 'info',
|
|
100
|
+
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (!fixEnabled) {
|
|
104
|
+
await ctx.reply('❌ The fix command is disabled on this bot instance.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (isOldMessage(ctx)) return;
|
|
108
|
+
// Issue #1922: a forwarded or replied-to /fix command must never be
|
|
109
|
+
// re-executed. Unlike /task, /fix takes no input from the replied message,
|
|
110
|
+
// so both cases are ignored.
|
|
111
|
+
if (isForwardedOrReply && isForwardedOrReply(ctx)) {
|
|
112
|
+
VERBOSE && console.log(`[VERBOSE] ${commandDisplay} ignored: forwarded or reply message`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!isGroupChat(ctx)) {
|
|
116
|
+
await ctx.reply(`❌ The ${commandDisplay} command only works in group chats. Please add this bot to a group and make it an admin.`, { reply_to_message_id: ctx.message.message_id });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!isTopicAuthorized(ctx)) {
|
|
120
|
+
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (isChatStopped(ctx.chat.id)) {
|
|
124
|
+
await safeReply(ctx, getStoppedChatRejectMessage(ctx.chat.id, 'Fix'), { reply_to_message_id: ctx.message.message_id });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const built = buildFixCommandArgs(ctx.message.text);
|
|
129
|
+
if (!built.repository) {
|
|
130
|
+
await safeReply(ctx, `❌ Missing GitHub repository URL. Usage: \`${commandDisplay} <github-repository-url> [options]\`\n\nExample: \`${commandDisplay} https://github.com/owner/repo\``, { reply_to_message_id: ctx.message.message_id });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { backend: perCommandIsolation, filteredArgs } = extractIsolationFromArgs(built.args);
|
|
135
|
+
if (perCommandIsolation && !isValidPerCommandIsolation(perCommandIsolation)) {
|
|
136
|
+
await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(perCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const modelError = validateFixModel(filteredArgs);
|
|
141
|
+
if (modelError) {
|
|
142
|
+
await safeReply(ctx, `❌ ${escapeMarkdown(modelError)}`, { reply_to_message_id: ctx.message.message_id });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const requester = buildUserMention({ user: ctx.from, parseMode: 'Markdown' });
|
|
147
|
+
const userOptionsRaw = built.args.slice(1).join(' ');
|
|
148
|
+
let infoBlock = `Requested by: ${requester}\nRepository: ${escapeMarkdown(built.repository.url)}`;
|
|
149
|
+
if (userOptionsRaw) infoBlock += `\n\n🛠 Options: ${escapeMarkdown(userOptionsRaw)}`;
|
|
150
|
+
|
|
151
|
+
const fixUrlContext = { owner: built.repository.owner, repo: built.repository.repo, normalized: built.repository.url };
|
|
152
|
+
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
|
|
153
|
+
const fixLocale = resolveLocale ? resolveLocale(ctx) : null;
|
|
154
|
+
const argsForExec = injectLanguageIfMissing(filteredArgs, fixLocale);
|
|
155
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'fix', argsForExec, infoBlock, perCommandIsolation || null, getFixToolFromArgs(argsForExec), fixUrlContext);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bot.command(
|
|
159
|
+
FIX_COMMAND_NAMES.map(command => new RegExp(`^${command}$`, 'i')),
|
|
160
|
+
handleFixCommand
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return { handleFixCommand, FIX_COMMAND_NAMES };
|
|
164
|
+
}
|
|
@@ -21,6 +21,7 @@ import { resolveMergeTargetItems } from './github-merge-targets.lib.mjs';
|
|
|
21
21
|
import { waitForPRReady as waitForPRReadyHelper } from './telegram-merge-wait.lib.mjs';
|
|
22
22
|
import { mergeQueue as mergeQueueConfig } from './config.lib.mjs';
|
|
23
23
|
import { getProgressBar } from './limits.lib.mjs';
|
|
24
|
+
import { cancellableSleep as cancellableSleepUntil } from './interruptible-sleep.lib.mjs';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Status enum for merge queue operations
|
|
@@ -495,7 +496,17 @@ export class MergeQueueProcessor {
|
|
|
495
496
|
try {
|
|
496
497
|
// Step 1: Check if PR is mergeable
|
|
497
498
|
item.status = MergeItemStatus.CHECKING_CI;
|
|
498
|
-
|
|
499
|
+
// Issue #2072: pass cancellation down so the UNKNOWN-mergeability retry delay aborts early
|
|
500
|
+
const mergeableCheck = await this.checkPRMergeable(this.owner, this.repo, item.pr.number, this.verbose, { isCancelled: () => this.isCancelled });
|
|
501
|
+
|
|
502
|
+
// Issue #2072: a cancel during the mergeability check must skip the PR, not fail it.
|
|
503
|
+
if (mergeableCheck.cancelled || this.isCancelled) {
|
|
504
|
+
item.status = MergeItemStatus.SKIPPED;
|
|
505
|
+
item.error = 'Cancelled';
|
|
506
|
+
this.stats.skipped++;
|
|
507
|
+
this.log(`Skipped PR #${item.pr.number}: cancelled during mergeability check`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
499
510
|
|
|
500
511
|
if (mergeableCheck.terminal) {
|
|
501
512
|
item.status = MergeItemStatus.FAILED;
|
|
@@ -893,7 +904,7 @@ export class MergeQueueProcessor {
|
|
|
893
904
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
894
905
|
return { outcome: 'error', error: error.message };
|
|
895
906
|
}
|
|
896
|
-
await this.
|
|
907
|
+
await this.sleep(pollInterval);
|
|
897
908
|
continue;
|
|
898
909
|
}
|
|
899
910
|
|
|
@@ -912,27 +923,12 @@ export class MergeQueueProcessor {
|
|
|
912
923
|
}
|
|
913
924
|
}
|
|
914
925
|
|
|
915
|
-
await this.
|
|
926
|
+
await this.sleep(pollInterval);
|
|
916
927
|
}
|
|
917
928
|
|
|
918
929
|
return { outcome: 'timeout' };
|
|
919
930
|
}
|
|
920
931
|
|
|
921
|
-
/**
|
|
922
|
-
* Issue #1807: Sleep helper that bails out as soon as cancellation is
|
|
923
|
-
* requested. Used by the auto-resolve poll loop so a `cancel()` call
|
|
924
|
-
* doesn't have to wait a full polling interval before taking effect.
|
|
925
|
-
*/
|
|
926
|
-
async cancellableSleep(ms) {
|
|
927
|
-
const step = Math.min(ms, 1000);
|
|
928
|
-
const deadline = Date.now() + ms;
|
|
929
|
-
while (Date.now() < deadline) {
|
|
930
|
-
if (this.isCancelled) return;
|
|
931
|
-
const remaining = deadline - Date.now();
|
|
932
|
-
await this.sleep(Math.min(step, remaining));
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
932
|
/**
|
|
937
933
|
* Wait for any active CI runs on the target branch to complete
|
|
938
934
|
* Issue #1307: Prevents merging while post-merge CI from previous merges is still running
|
|
@@ -1456,10 +1452,17 @@ export class MergeQueueProcessor {
|
|
|
1456
1452
|
}
|
|
1457
1453
|
|
|
1458
1454
|
/**
|
|
1459
|
-
* Sleep helper
|
|
1455
|
+
* Sleep helper.
|
|
1456
|
+
*
|
|
1457
|
+
* Issue #2072: this is the single sleep primitive for the queue, and it is
|
|
1458
|
+
* cancellable — it returns as soon as `cancel()` is called (checked every
|
|
1459
|
+
* 100ms) or SIGINT/SIGTERM arrives, instead of sleeping out the full delay.
|
|
1460
|
+
* Every wait in the queue routes through here, so no stage of `/merge` can
|
|
1461
|
+
* hold up a cancel by more than ~100ms. This supersedes the separate
|
|
1462
|
+
* `cancellableSleep` helper added for #1807.
|
|
1460
1463
|
*/
|
|
1461
1464
|
sleep(ms) {
|
|
1462
|
-
return
|
|
1465
|
+
return cancellableSleepUntil(ms, () => this.isCancelled);
|
|
1463
1466
|
}
|
|
1464
1467
|
}
|
|
1465
1468
|
|
|
@@ -65,8 +65,14 @@ export async function waitForPRReady(processor, item, initialCheck, options) {
|
|
|
65
65
|
await processor.onProgress(processor.getProgressUpdate());
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Issue #2072: `processor.sleep` returns early on cancel. The poll interval defaults
|
|
69
|
+
// to 5 minutes, which previously kept `/merge` running long after Cancel was pressed.
|
|
68
70
|
await processor.sleep(pollIntervalMs);
|
|
69
|
-
|
|
71
|
+
if (processor.isCancelled) {
|
|
72
|
+
return { success: false, status: 'cancelled', error: 'Cancelled' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
latestCheck = await processor.checkPRMergeable(processor.owner, processor.repo, item.pr.number, processor.verbose, { isCancelled: () => processor.isCancelled });
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|
|
@@ -23,7 +23,7 @@ export function buildSolveQueuedMessage({ locale = null, tool = 'claude', positi
|
|
|
23
23
|
return message;
|
|
24
24
|
}
|
|
25
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 = '' } = {}) {
|
|
26
|
+
export function buildTelegramHelpMessage({ locale = null, chatId, chatType = '', chatTitle = '', topicId = null, isStopped = false, stopInfo = null, stopReason = '', solveEnabled = true, taskEnabled = true, fixEnabled = true, hiveEnabled = true, solveOverrides = [], hiveOverrides = [], showLimitsEnabled = false, isolationBackend = null, modelDescription = '', restrictedMode = false, authorized = null, allowTopicHint = '' } = {}) {
|
|
27
27
|
const message = [];
|
|
28
28
|
addLine(message, 'telegram.help_title', {}, locale);
|
|
29
29
|
message.push('');
|
|
@@ -71,6 +71,16 @@ export function buildTelegramHelpMessage({ locale = null, chatId, chatType = '',
|
|
|
71
71
|
message.push('');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
if (fixEnabled) {
|
|
75
|
+
addLine(message, 'telegram.help_fix_enabled', {}, locale);
|
|
76
|
+
addLine(message, 'telegram.help_fix_usage', {}, locale);
|
|
77
|
+
addLine(message, 'telegram.help_fix_example', {}, locale);
|
|
78
|
+
message.push('');
|
|
79
|
+
} else {
|
|
80
|
+
addLine(message, 'telegram.help_fix_disabled', {}, locale);
|
|
81
|
+
message.push('');
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
if (hiveEnabled) {
|
|
75
85
|
addLine(message, 'telegram.help_hive_enabled', {}, locale);
|
|
76
86
|
addLine(message, 'telegram.help_hive_usage', {}, locale);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// CLI configuration module for the telegram-bot command.
|
|
2
|
+
// Extracted from telegram-bot.mjs so the option surface can be reviewed, tested
|
|
3
|
+
// and reused without loading the bot itself.
|
|
4
|
+
//
|
|
5
|
+
// Defaults read the environment through getenv() when createYargsConfig() runs,
|
|
6
|
+
// so callers must load .env/.lenv configuration before calling it.
|
|
7
|
+
|
|
8
|
+
import { getenv } from './cli-arguments.lib.mjs';
|
|
9
|
+
|
|
10
|
+
export const createYargsConfig = yargsInstance =>
|
|
11
|
+
yargsInstance
|
|
12
|
+
.usage('Usage: hive-telegram-bot [options]')
|
|
13
|
+
.option('configuration', {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'LINO configuration string for environment variables',
|
|
16
|
+
alias: 'c',
|
|
17
|
+
default: getenv('TELEGRAM_CONFIGURATION', ''),
|
|
18
|
+
})
|
|
19
|
+
.option('token', {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Telegram bot token from @BotFather',
|
|
22
|
+
alias: 't',
|
|
23
|
+
default: getenv('TELEGRAM_BOT_TOKEN', ''),
|
|
24
|
+
})
|
|
25
|
+
.option('allowedChats', {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Allowed chat IDs in lino notation, e.g., "(\n 123456789\n 987654321\n)"',
|
|
28
|
+
alias: 'allowed-chats',
|
|
29
|
+
default: getenv('TELEGRAM_ALLOWED_CHATS', ''),
|
|
30
|
+
})
|
|
31
|
+
.option('allowedTopics', {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Allowed topic IDs in Links Notation format "chatId topicId" pairs',
|
|
34
|
+
alias: 'allowed-topics',
|
|
35
|
+
default: getenv('TELEGRAM_ALLOWED_TOPICS', ''),
|
|
36
|
+
})
|
|
37
|
+
.option('solveOverrides', {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Override options for /solve command in lino notation, e.g., "(\n --auto-continue\n --attach-logs\n)"',
|
|
40
|
+
alias: 'solve-overrides',
|
|
41
|
+
default: getenv('TELEGRAM_SOLVE_OVERRIDES', ''),
|
|
42
|
+
})
|
|
43
|
+
.option('hiveOverrides', {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Override options for /hive command in lino notation, e.g., "(\n --verbose\n --all-issues\n)"',
|
|
46
|
+
alias: 'hive-overrides',
|
|
47
|
+
default: getenv('TELEGRAM_HIVE_OVERRIDES', ''),
|
|
48
|
+
})
|
|
49
|
+
.option('solve', {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
description: 'Enable /solve command (use --no-solve to disable)',
|
|
52
|
+
default: getenv('TELEGRAM_SOLVE', 'true') !== 'false',
|
|
53
|
+
})
|
|
54
|
+
.option('hive', {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: 'Enable /hive command (use --no-hive to disable)',
|
|
57
|
+
default: getenv('TELEGRAM_HIVE', 'true') !== 'false',
|
|
58
|
+
})
|
|
59
|
+
.option('task', {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
description: 'Enable /task and /split commands (use --no-task to disable)',
|
|
62
|
+
default: getenv('TELEGRAM_TASK', 'true') !== 'false',
|
|
63
|
+
})
|
|
64
|
+
.option('fix', {
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
description: 'Enable /fix command (use --no-fix to disable)',
|
|
67
|
+
default: getenv('TELEGRAM_FIX', 'true') !== 'false',
|
|
68
|
+
})
|
|
69
|
+
.option('auth', {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
description: 'Enable experimental private /auth command for allowlisted chat owners (use --no-auth to disable)',
|
|
72
|
+
default: getenv('TELEGRAM_AUTH', 'true') !== 'false',
|
|
73
|
+
})
|
|
74
|
+
.option('dryRun', {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description: 'Validate configuration and options without starting the bot',
|
|
77
|
+
alias: 'dry-run',
|
|
78
|
+
default: false,
|
|
79
|
+
})
|
|
80
|
+
.option('verbose', {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
description: 'Enable verbose logging for debugging',
|
|
83
|
+
alias: 'v',
|
|
84
|
+
default: getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true',
|
|
85
|
+
})
|
|
86
|
+
.option('autoStartScreenWatchMessage', { type: 'boolean', description: 'Experimental: auto-start separate /terminal_watch messages for public /solve sessions', alias: 'auto-start-screen-watch-message', default: getenv('TELEGRAM_AUTO_START_SCREEN_WATCH_MESSAGE', getenv('TELEGRAM_AUTO_WATCH_MESSAGE', 'false')) === 'true' })
|
|
87
|
+
// Issue #594: bot-owner toggle for --show-limits virtual option in /solve and /hive.
|
|
88
|
+
.option('showLimits', { type: 'boolean', description: 'Experimental: allow /solve and /hive callers to use --show-limits to embed Claude/Codex usage at start, end, and delta in the completion message', alias: 'show-limits', default: getenv('TELEGRAM_SHOW_LIMITS', 'true') !== 'false' })
|
|
89
|
+
.option('isolation', { type: 'string', description: "Isolation backend (screen/tmux/docker). Defaults to 'docker' so Telegram-bot work sessions run in Docker isolation; pass --isolation '' (or set TELEGRAM_ISOLATION='') to disable.", default: getenv('TELEGRAM_ISOLATION', 'docker') })
|
|
90
|
+
.help('h')
|
|
91
|
+
.alias('h', 'help')
|
|
92
|
+
.parserConfiguration({
|
|
93
|
+
'boolean-negation': true,
|
|
94
|
+
'strip-dashed': true, // Remove dashed keys from argv to simplify validation
|
|
95
|
+
})
|
|
96
|
+
.strict(); // Enable strict mode to reject unknown options (consistent with solve.mjs and hive.mjs)
|