@link-assistant/hive-mind 1.56.4 ā 1.56.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 +2 -2
- package/src/codex.lib.mjs +99 -2
- package/src/telegram-bot.mjs +32 -42
- package/src/telegram-solve-command.lib.mjs +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.56.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e4037e1: Support Telegram solve and hive commands when options are placed before the GitHub URL.
|
|
8
|
+
|
|
9
|
+
## 1.56.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0447110: Treat structured Codex error events as failed tool executions even when the Codex process exits with code 0.
|
|
14
|
+
|
|
3
15
|
## 1.56.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.56.
|
|
3
|
+
"version": "1.56.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",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
18
|
+
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-hive-screens.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-telegram-options-before-url.mjs && node tests/test-telegram-bot-configuration-isolation-links-notation.mjs && node tests/test-extract-isolation-from-args.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
19
19
|
"test:queue": "node tests/solve-queue.test.mjs",
|
|
20
20
|
"test:limits-display": "node tests/limits-display.test.mjs",
|
|
21
21
|
"test:usage-limit": "node tests/test-usage-limit.mjs",
|
package/src/codex.lib.mjs
CHANGED
|
@@ -183,6 +183,57 @@ const upsertCodexItemError = (itemErrors, item) => {
|
|
|
183
183
|
});
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
+
const unwrapCodexErrorMessage = value => {
|
|
187
|
+
if (!value) return '';
|
|
188
|
+
if (typeof value !== 'string') {
|
|
189
|
+
if (typeof value?.error?.message === 'string') return unwrapCodexErrorMessage(value.error.message);
|
|
190
|
+
if (typeof value?.message === 'string') return unwrapCodexErrorMessage(value.message);
|
|
191
|
+
return String(value);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let text = value.trim();
|
|
195
|
+
for (let i = 0; i < 3; i++) {
|
|
196
|
+
if (!text.startsWith('{') && !text.startsWith('[')) break;
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(text);
|
|
199
|
+
if (typeof parsed?.error?.message === 'string') return unwrapCodexErrorMessage(parsed.error.message);
|
|
200
|
+
if (typeof parsed?.message === 'string') {
|
|
201
|
+
text = parsed.message.trim();
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
return JSON.stringify(parsed);
|
|
205
|
+
} catch {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return text;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const getCodexErrorEventSummary = codexJsonState => {
|
|
213
|
+
const events = [];
|
|
214
|
+
const addEvents = (type, items = []) => {
|
|
215
|
+
for (const item of items) {
|
|
216
|
+
const message = unwrapCodexErrorMessage(item?.message);
|
|
217
|
+
events.push({ type, message: message || 'Codex emitted an error event' });
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
addEvents('item', codexJsonState?.itemErrors);
|
|
222
|
+
addEvents('turn', codexJsonState?.turnFailures);
|
|
223
|
+
addEvents('stream', codexJsonState?.streamErrors);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
hasError: events.length > 0,
|
|
227
|
+
message: events[0]?.message || null,
|
|
228
|
+
events,
|
|
229
|
+
counts: {
|
|
230
|
+
item: codexJsonState?.itemErrors?.length || 0,
|
|
231
|
+
turn: codexJsonState?.turnFailures?.length || 0,
|
|
232
|
+
stream: codexJsonState?.streamErrors?.length || 0,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
186
237
|
export const parseCodexExecJsonOutput = (output, state = {}, requestedModelId = null) => {
|
|
187
238
|
const nextState = {
|
|
188
239
|
sessionId: state.sessionId || null,
|
|
@@ -597,7 +648,7 @@ export const executeCodex = async params => {
|
|
|
597
648
|
};
|
|
598
649
|
|
|
599
650
|
export const executeCodexCommand = async params => {
|
|
600
|
-
const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, codexPath, $, owner, repo, prNumber } = params;
|
|
651
|
+
const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, codexPath, $, owner, repo, prNumber, calculatePricing = calculateCodexPricing } = params;
|
|
601
652
|
|
|
602
653
|
const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
603
654
|
|
|
@@ -852,7 +903,7 @@ export const executeCodexCommand = async params => {
|
|
|
852
903
|
}
|
|
853
904
|
|
|
854
905
|
const firstActualModelId = mappedModel;
|
|
855
|
-
const pricingInfo = firstActualModelId ? await
|
|
906
|
+
const pricingInfo = firstActualModelId ? await calculatePricing(firstActualModelId, codexJsonState.tokenUsage.stepCount > 0 ? codexJsonState.tokenUsage : null) : null;
|
|
856
907
|
if (pricingInfo?.totalCostUSD !== null && pricingInfo?.totalCostUSD !== undefined) {
|
|
857
908
|
await log(`š° Codex public pricing estimate: $${new Decimal(pricingInfo.totalCostUSD).toFixed(6)}`, { verbose: true });
|
|
858
909
|
if (pricingInfo.usesLongContextPricing) {
|
|
@@ -876,6 +927,49 @@ export const executeCodexCommand = async params => {
|
|
|
876
927
|
throw error;
|
|
877
928
|
}
|
|
878
929
|
|
|
930
|
+
const codexErrorSummary = getCodexErrorEventSummary(codexJsonState);
|
|
931
|
+
if (codexErrorSummary.hasError) {
|
|
932
|
+
const limitInfo = detectUsageLimit(codexErrorSummary.message || lastMessage);
|
|
933
|
+
if (limitInfo.isUsageLimit) {
|
|
934
|
+
limitReached = true;
|
|
935
|
+
limitResetTime = limitInfo.resetTime;
|
|
936
|
+
|
|
937
|
+
const messageLines = formatUsageLimitMessage({
|
|
938
|
+
tool: 'OpenAI Codex',
|
|
939
|
+
resetTime: limitInfo.resetTime,
|
|
940
|
+
sessionId,
|
|
941
|
+
resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
for (const line of messageLines) {
|
|
945
|
+
await log(line, { level: 'warning' });
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
await log(`\n\nā Codex emitted error event: ${codexErrorSummary.message}`, { level: 'error' });
|
|
949
|
+
await log(` Error events: item=${codexErrorSummary.counts.item}, turn=${codexErrorSummary.counts.turn}, stream=${codexErrorSummary.counts.stream}`, { level: 'error' });
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const resourcesAfter = await getResourceSnapshot();
|
|
953
|
+
await log('\nš System resources after execution:', { verbose: true });
|
|
954
|
+
await log(` Memory: ${resourcesAfter.memory.split('\n')[1]}`, { verbose: true });
|
|
955
|
+
await log(` Load: ${resourcesAfter.load}`, { verbose: true });
|
|
956
|
+
|
|
957
|
+
return {
|
|
958
|
+
success: false,
|
|
959
|
+
sessionId,
|
|
960
|
+
limitReached,
|
|
961
|
+
limitResetTime,
|
|
962
|
+
pricingInfo,
|
|
963
|
+
publicPricingEstimate: pricingInfo?.totalCostUSD ?? null,
|
|
964
|
+
resultModelUsage,
|
|
965
|
+
subAgentCalls: codexJsonState.subAgentCalls.length > 0 ? codexJsonState.subAgentCalls : null,
|
|
966
|
+
codexJsonDetails: codexJsonState,
|
|
967
|
+
errorInfo: codexErrorSummary,
|
|
968
|
+
result: codexErrorSummary.message,
|
|
969
|
+
resultSummary: lastTextContent || null, // Issue #1263: Use last text content from JSON output stream
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
879
973
|
if (exitCode !== 0) {
|
|
880
974
|
// Check for usage limit errors first (more specific)
|
|
881
975
|
const limitInfo = detectUsageLimit(lastMessage);
|
|
@@ -915,6 +1009,7 @@ export const executeCodexCommand = async params => {
|
|
|
915
1009
|
resultModelUsage,
|
|
916
1010
|
subAgentCalls: codexJsonState.subAgentCalls.length > 0 ? codexJsonState.subAgentCalls : null,
|
|
917
1011
|
codexJsonDetails: codexJsonState,
|
|
1012
|
+
errorInfo: getCodexErrorEventSummary(codexJsonState),
|
|
918
1013
|
resultSummary: lastTextContent || null, // Issue #1263: Use last text content from JSON output stream
|
|
919
1014
|
};
|
|
920
1015
|
}
|
|
@@ -965,6 +1060,8 @@ export const executeCodexCommand = async params => {
|
|
|
965
1060
|
limitResetTime: null,
|
|
966
1061
|
pricingInfo: null,
|
|
967
1062
|
publicPricingEstimate: null,
|
|
1063
|
+
errorInfo: { hasError: true, message: error.message, events: [{ type: 'exception', message: error.message }], counts: { item: 0, turn: 0, stream: 0 } },
|
|
1064
|
+
result: error.message,
|
|
968
1065
|
resultSummary: null, // Issue #1263: No result summary available on error
|
|
969
1066
|
};
|
|
970
1067
|
} finally {
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -45,7 +45,7 @@ const { formatUsageMessage, formatCodexLimitsSection, getAllCachedLimits } = awa
|
|
|
45
45
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
46
46
|
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
47
47
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
48
|
-
const { applySolveToolAlias, getSolveCommandNameFromText, getSolveToolAliasFromText, parseCommandArgs, SOLVE_COMMAND_NAMES } = await import('./telegram-solve-command.lib.mjs');
|
|
48
|
+
const { applySolveToolAlias, getFirstParsedPositionalArg, getSolveCommandNameFromText, getSolveToolAliasFromText, moveArgumentToFront, parseArgsWithYargs, parseCommandArgs, SOLVE_COMMAND_NAMES } = await import('./telegram-solve-command.lib.mjs');
|
|
49
49
|
const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
|
|
50
50
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
51
51
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
@@ -500,11 +500,18 @@ function mergeArgsWithOverrides(userArgs, overrides) {
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
/** Validate GitHub URL for Telegram bot commands. Returns { valid, error?, parsed?, normalizedUrl? } */
|
|
503
|
-
function
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
503
|
+
async function getCommandUrlArg(args, createYargsConfig, positionalNames) {
|
|
504
|
+
const parsedUrl = createYargsConfig ? await getFirstParsedPositionalArg(args, yargs, createYargsConfig, positionalNames) : null;
|
|
505
|
+
if (parsedUrl) return parsedUrl;
|
|
506
|
+
return args.find(arg => cleanNonPrintableChars(arg).includes('github.com')) || (args[0] && !args[0].startsWith('-') ? args[0] : null);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function validateGitHubUrl(args, options = {}) {
|
|
510
|
+
const { allowedTypes = ['issue', 'pull'], commandName = 'solve', createYargsConfig = null, positionalNames = [] } = options;
|
|
511
|
+
const rawUrl = await getCommandUrlArg(args, createYargsConfig, positionalNames);
|
|
512
|
+
if (!rawUrl) return { valid: false, error: `Missing GitHub URL. Usage: /${commandName} <github-url> [options]` };
|
|
506
513
|
// Issue #1102: Clean non-printable chars (Zero-Width Space, BOM, etc.) from URLs
|
|
507
|
-
const url = cleanNonPrintableChars(
|
|
514
|
+
const url = cleanNonPrintableChars(rawUrl);
|
|
508
515
|
if (!url.includes('github.com')) return { valid: false, error: 'First argument must be a GitHub URL' };
|
|
509
516
|
const parsed = parseGitHubUrl(url);
|
|
510
517
|
if (!parsed.valid) return { valid: false, error: parsed.error || 'Invalid GitHub URL', suggestion: parsed.suggestion };
|
|
@@ -842,11 +849,12 @@ async function handleSolveCommand(ctx) {
|
|
|
842
849
|
// Issue #1325: Support all options via /solve command when replying (e.g., "/solve --model opus")
|
|
843
850
|
const isReply = message.reply_to_message && message.reply_to_message.message_id && !message.reply_to_message.forum_topic_created;
|
|
844
851
|
|
|
845
|
-
// Check if
|
|
846
|
-
|
|
847
|
-
const
|
|
852
|
+
// Check if yargs sees a command URL. If not, try to extract it from the replied message.
|
|
853
|
+
const commandUrlArg = await getCommandUrlArg(userArgs, createSolveYargsConfig, ['issue-url']);
|
|
854
|
+
const commandUrlText = commandUrlArg ? cleanNonPrintableChars(commandUrlArg) : '';
|
|
855
|
+
const commandHasUrl = commandUrlText.includes('github.com') || /^https?:\/\//.test(commandUrlText);
|
|
848
856
|
|
|
849
|
-
if (isReply && !
|
|
857
|
+
if (isReply && !commandHasUrl) {
|
|
850
858
|
if (VERBOSE) {
|
|
851
859
|
console.log('[VERBOSE] /solve is a reply without URL in args, extracting from replied message...');
|
|
852
860
|
console.log('[VERBOSE] User args:', userArgs);
|
|
@@ -883,7 +891,13 @@ async function handleSolveCommand(ctx) {
|
|
|
883
891
|
|
|
884
892
|
userArgs = applySolveToolAlias(userArgs, solveToolAlias);
|
|
885
893
|
|
|
886
|
-
const
|
|
894
|
+
const { malformed, errors: malformedErrors } = detectMalformedFlags(userArgs);
|
|
895
|
+
if (malformed.length > 0) {
|
|
896
|
+
await safeReply(ctx, `ā ${escapeMarkdown(malformedErrors.join('\n'))}\n\nPlease check your option syntax.`, { reply_to_message_id: ctx.message.message_id });
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const validation = await validateGitHubUrl(userArgs, { createYargsConfig: createSolveYargsConfig, positionalNames: ['issue-url'] });
|
|
887
901
|
if (!validation.valid) {
|
|
888
902
|
let errorMsg = `ā ${validation.error}`;
|
|
889
903
|
if (validation.suggestion) {
|
|
@@ -893,6 +907,7 @@ async function handleSolveCommand(ctx) {
|
|
|
893
907
|
await safeReply(ctx, errorMsg, { reply_to_message_id: ctx.message.message_id });
|
|
894
908
|
return;
|
|
895
909
|
}
|
|
910
|
+
userArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
|
|
896
911
|
const { backend: solvePerCommandIsolation, filteredArgs: userArgsWithoutIsolation } = extractIsolationFromArgs(userArgs); // issue #1534
|
|
897
912
|
if (solvePerCommandIsolation && !isValidPerCommandIsolation(solvePerCommandIsolation)) {
|
|
898
913
|
await safeReply(ctx, `ā Invalid --isolation value '${escapeMarkdown(solvePerCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
@@ -928,27 +943,14 @@ async function handleSolveCommand(ctx) {
|
|
|
928
943
|
await safeReply(ctx, `ā ${escapeMarkdown(branchError)}`, { reply_to_message_id: ctx.message.message_id });
|
|
929
944
|
return;
|
|
930
945
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
await safeReply(ctx, `ā ${escapeMarkdown(malformedErrors.join('\n'))}\n\nPlease check your option syntax.`, { reply_to_message_id: ctx.message.message_id });
|
|
946
|
+
const { malformed: mergedMalformed, errors: mergedMalformedErrors } = detectMalformedFlags(args);
|
|
947
|
+
if (mergedMalformed.length > 0) {
|
|
948
|
+
await safeReply(ctx, `ā ${escapeMarkdown(mergedMalformedErrors.join('\n'))}\n\nPlease check your option syntax.`, { reply_to_message_id: ctx.message.message_id });
|
|
935
949
|
return;
|
|
936
950
|
}
|
|
937
951
|
// Validate merged arguments using solve's yargs config
|
|
938
952
|
try {
|
|
939
|
-
|
|
940
|
-
const testYargs = createSolveYargsConfig(yargs());
|
|
941
|
-
|
|
942
|
-
// Configure yargs to throw errors instead of trying to exit the process
|
|
943
|
-
// This prevents confusing error messages when validation fails but execution continues
|
|
944
|
-
let failureMessage = null;
|
|
945
|
-
testYargs.exitProcess(false).fail((msg, err) => {
|
|
946
|
-
// Capture the failure message instead of letting yargs print it
|
|
947
|
-
failureMessage = msg || (err && err.message) || 'Unknown validation error';
|
|
948
|
-
throw new Error(failureMessage);
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
testYargs.parse(args);
|
|
953
|
+
await parseArgsWithYargs(args, yargs, createSolveYargsConfig);
|
|
952
954
|
} catch (error) {
|
|
953
955
|
await safeReply(ctx, `ā Invalid options: ${escapeMarkdown(error.message || String(error))}\n\nUse /help to see available options`, {
|
|
954
956
|
reply_to_message_id: ctx.message.message_id,
|
|
@@ -1095,7 +1097,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1095
1097
|
const userArgs = parseCommandArgs(ctx.message.text);
|
|
1096
1098
|
|
|
1097
1099
|
// Issue #1102: Allow issues_list/pulls_list URLs and normalize to repo URLs
|
|
1098
|
-
const validation = validateGitHubUrl(userArgs, { allowedTypes: ['repo', 'organization', 'user', 'issues_list', 'pulls_list'], commandName: 'hive' });
|
|
1100
|
+
const validation = await validateGitHubUrl(userArgs, { allowedTypes: ['repo', 'organization', 'user', 'issues_list', 'pulls_list'], commandName: 'hive', createYargsConfig: createHiveYargsConfig, positionalNames: ['github-url'] });
|
|
1099
1101
|
if (!validation.valid) {
|
|
1100
1102
|
let errorMsg = `ā ${validation.error}`;
|
|
1101
1103
|
if (validation.suggestion) errorMsg += `\n\nš” Did you mean: \`${escapeMarkdown(validation.suggestion)}\``;
|
|
@@ -1104,7 +1106,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1104
1106
|
return;
|
|
1105
1107
|
}
|
|
1106
1108
|
// Normalize issues_list/pulls_list to base repo URL, or use cleaned URL
|
|
1107
|
-
let normalizedArgs =
|
|
1109
|
+
let normalizedArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
|
|
1108
1110
|
const p = validation.parsed;
|
|
1109
1111
|
if (p && (p.type === 'issues_list' || p.type === 'pulls_list')) {
|
|
1110
1112
|
normalizedArgs[0] = `https://github.com/${p.owner}/${p.repo}`;
|
|
@@ -1149,19 +1151,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1149
1151
|
|
|
1150
1152
|
// Validate merged arguments using hive's yargs config
|
|
1151
1153
|
try {
|
|
1152
|
-
|
|
1153
|
-
const testYargs = createHiveYargsConfig(yargs());
|
|
1154
|
-
|
|
1155
|
-
// Configure yargs to throw errors instead of trying to exit the process
|
|
1156
|
-
// This prevents confusing error messages when validation fails but execution continues
|
|
1157
|
-
let failureMessage = null;
|
|
1158
|
-
testYargs.exitProcess(false).fail((msg, err) => {
|
|
1159
|
-
// Capture the failure message instead of letting yargs print it
|
|
1160
|
-
failureMessage = msg || (err && err.message) || 'Unknown validation error';
|
|
1161
|
-
throw new Error(failureMessage);
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
testYargs.parse(args);
|
|
1154
|
+
await parseArgsWithYargs(args, yargs, createHiveYargsConfig);
|
|
1165
1155
|
} catch (error) {
|
|
1166
1156
|
await safeReply(ctx, `ā Invalid options: ${escapeMarkdown(error.message || String(error))}\n\nUse /help to see available options`, {
|
|
1167
1157
|
reply_to_message_id: ctx.message.message_id,
|
|
@@ -59,6 +59,64 @@ export function parseCommandArgs(text) {
|
|
|
59
59
|
return args;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function toCamelCaseOptionName(name) {
|
|
63
|
+
return name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getYargsPositionalArg(argv, positionalNames = []) {
|
|
67
|
+
if (!argv || typeof argv !== 'object') return null;
|
|
68
|
+
|
|
69
|
+
for (const name of positionalNames) {
|
|
70
|
+
const aliases = [name, toCamelCaseOptionName(name)];
|
|
71
|
+
for (const alias of aliases) {
|
|
72
|
+
if (typeof argv[alias] === 'string' && argv[alias].trim()) return argv[alias];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (Array.isArray(argv._)) {
|
|
77
|
+
return argv._.find(value => typeof value === 'string' && value.trim()) || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function parseArgsWithYargs(args, yargsFactory, createYargsConfig) {
|
|
84
|
+
const originalStderrWrite = process.stderr.write;
|
|
85
|
+
process.stderr.write = (_chunk, encoding, callback) => {
|
|
86
|
+
if (typeof encoding === 'function') encoding();
|
|
87
|
+
else if (typeof callback === 'function') callback();
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
const parser = createYargsConfig(yargsFactory());
|
|
92
|
+
parser
|
|
93
|
+
.exitProcess(false)
|
|
94
|
+
.showHelpOnFail(false)
|
|
95
|
+
.fail((msg, err) => {
|
|
96
|
+
throw err || new Error(msg || 'Invalid arguments');
|
|
97
|
+
});
|
|
98
|
+
return await parser.parse(args);
|
|
99
|
+
} finally {
|
|
100
|
+
process.stderr.write = originalStderrWrite;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function getFirstParsedPositionalArg(args, yargsFactory, createYargsConfig, positionalNames = []) {
|
|
105
|
+
try {
|
|
106
|
+
return getYargsPositionalArg(await parseArgsWithYargs(args, yargsFactory, createYargsConfig), positionalNames);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function moveArgumentToFront(args, target, normalize = value => value) {
|
|
113
|
+
if (!target) return [...args];
|
|
114
|
+
const normalizedTarget = normalize(target);
|
|
115
|
+
const index = args.findIndex(arg => normalize(arg) === normalizedTarget);
|
|
116
|
+
if (index < 0) return [normalizedTarget, ...args];
|
|
117
|
+
return [normalizedTarget, ...args.slice(0, index), ...args.slice(index + 1)];
|
|
118
|
+
}
|
|
119
|
+
|
|
62
120
|
export function getSolveCommandNameFromText(text) {
|
|
63
121
|
if (!text || typeof text !== 'string') {
|
|
64
122
|
return null;
|