@link-assistant/hive-mind 1.8.0 → 1.9.1
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 +32 -0
- package/package.json +3 -2
- package/src/claude.lib.mjs +89 -2
- package/src/claude.prompts.lib.mjs +8 -2
- package/src/config.lib.mjs +76 -1
- package/src/github.lib.mjs +1 -0
- package/src/hive.config.lib.mjs +12 -2
- package/src/hive.mjs +2 -0
- package/src/option-suggestions.lib.mjs +3 -0
- package/src/solve.config.lib.mjs +17 -2
- package/src/telegram-accept-invitations.lib.mjs +142 -34
- package/src/telegram-bot.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 06da02c: Improve /accept_invites command output with grouped items and real-time updates
|
|
8
|
+
|
|
9
|
+
**Changes:**
|
|
10
|
+
- Group output by "Repositories:" and "Organizations:" instead of repeating "Repository:" for each item
|
|
11
|
+
- Add clickable GitHub links for each repository and organization
|
|
12
|
+
- Implement real-time message updates after each invitation is processed
|
|
13
|
+
- Show progress indicator (e.g., "Processing GitHub Invitations (3/10)") during processing
|
|
14
|
+
|
|
15
|
+
Fixes #1148
|
|
16
|
+
|
|
17
|
+
## 1.9.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- e15f307: Add bidirectional translation between --think and --thinking-budget options for Claude Code
|
|
22
|
+
|
|
23
|
+
**Changes:**
|
|
24
|
+
- Add 'off' option to --think values: ['off', 'low', 'medium', 'high', 'max']
|
|
25
|
+
- Add --thinking-budget-claude-minimum-version option (default: 2.1.12)
|
|
26
|
+
- For Claude Code >= 2.1.12: translate --think to --thinking-budget (off→0, low→8000, medium→16000, high→24000, max→31999)
|
|
27
|
+
- For Claude Code < 2.1.12: translate --thinking-budget back to --think thinking keywords
|
|
28
|
+
- Both options now coexist and support all Claude Code versions
|
|
29
|
+
|
|
30
|
+
**Rationale:**
|
|
31
|
+
Claude Code v2.1.12+ no longer responds to thinking keywords (think, think hard, ultrathink) because extended thinking is enabled by default. The only way to control thinking budget programmatically is via MAX_THINKING_TOKENS environment variable.
|
|
32
|
+
|
|
33
|
+
Fixes #1146
|
|
34
|
+
|
|
3
35
|
## 1.8.0
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"@sentry/node": "^10.15.0",
|
|
69
69
|
"@sentry/profiling-node": "^10.15.0",
|
|
70
70
|
"dayjs": "^1.11.19",
|
|
71
|
-
"secretlint": "^11.2.5"
|
|
71
|
+
"secretlint": "^11.2.5",
|
|
72
|
+
"semver": "^7.7.3"
|
|
72
73
|
},
|
|
73
74
|
"lint-staged": {
|
|
74
75
|
"*.{js,mjs,json,md}": [
|
package/src/claude.lib.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const path = (await use('path')).default;
|
|
|
10
10
|
// Import log from general lib
|
|
11
11
|
import { log } from './lib.mjs';
|
|
12
12
|
import { reportError } from './sentry.lib.mjs';
|
|
13
|
-
import { timeouts, retryLimits, claudeCode, getClaudeEnv } from './config.lib.mjs';
|
|
13
|
+
import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET } from './config.lib.mjs';
|
|
14
14
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
15
15
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
16
16
|
import { displayBudgetStats } from './claude.budget-stats.lib.mjs';
|
|
@@ -68,6 +68,8 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
68
68
|
const versionResult = await $`timeout ${Math.floor(timeouts.claudeCli / 6000)} claude --version`;
|
|
69
69
|
if (versionResult.code === 0) {
|
|
70
70
|
const version = versionResult.stdout?.toString().trim();
|
|
71
|
+
// Store the version for thinking settings translation (issue #1146)
|
|
72
|
+
detectedClaudeVersion = version;
|
|
71
73
|
if (retryCount === 0) {
|
|
72
74
|
await log(`📦 Claude CLI version: ${version}`);
|
|
73
75
|
}
|
|
@@ -219,6 +221,76 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
219
221
|
// handleClaudeRuntimeSwitch is imported from ./claude.runtime-switch.lib.mjs (see issue #1141)
|
|
220
222
|
// Re-export it for backwards compatibility
|
|
221
223
|
export { handleClaudeRuntimeSwitch };
|
|
224
|
+
|
|
225
|
+
// Store Claude Code version globally (set during validation)
|
|
226
|
+
let detectedClaudeVersion = null;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get the detected Claude Code version
|
|
230
|
+
* @returns {string|null} The detected version or null if not yet detected
|
|
231
|
+
*/
|
|
232
|
+
export const getClaudeVersion = () => detectedClaudeVersion;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Set the detected Claude Code version (called during validation)
|
|
236
|
+
* @param {string} version - The detected version string
|
|
237
|
+
*/
|
|
238
|
+
export const setClaudeVersion = version => {
|
|
239
|
+
detectedClaudeVersion = version;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Resolve thinking settings based on --think and --thinking-budget options
|
|
244
|
+
* Handles translation between thinking levels and token budgets based on Claude Code version
|
|
245
|
+
* @param {Object} argv - Command line arguments
|
|
246
|
+
* @param {Function} log - Logging function
|
|
247
|
+
* @returns {Object} { thinkingBudget, thinkLevel, translation, maxBudget } - Resolved settings
|
|
248
|
+
*/
|
|
249
|
+
export const resolveThinkingSettings = async (argv, log) => {
|
|
250
|
+
const minVersion = argv.thinkingBudgetClaudeMinimumVersion || '2.1.12';
|
|
251
|
+
const version = detectedClaudeVersion || '0.0.0'; // Assume old version if not detected
|
|
252
|
+
const isNewVersion = supportsThinkingBudget(version, minVersion);
|
|
253
|
+
|
|
254
|
+
// Get max thinking budget from argv or use default (see issue #1146)
|
|
255
|
+
const maxBudget = argv.maxThinkingBudget ?? DEFAULT_MAX_THINKING_BUDGET;
|
|
256
|
+
|
|
257
|
+
// Get thinking level mappings calculated from maxBudget
|
|
258
|
+
const thinkingLevelToTokens = getThinkingLevelToTokens(maxBudget);
|
|
259
|
+
const tokensToThinkingLevel = getTokensToThinkingLevel(maxBudget);
|
|
260
|
+
|
|
261
|
+
let thinkingBudget = argv.thinkingBudget;
|
|
262
|
+
let thinkLevel = argv.think;
|
|
263
|
+
let translation = null;
|
|
264
|
+
|
|
265
|
+
if (isNewVersion) {
|
|
266
|
+
// Claude Code >= 2.1.12: translate --think to --thinking-budget
|
|
267
|
+
if (thinkLevel !== undefined && thinkingBudget === undefined) {
|
|
268
|
+
thinkingBudget = thinkingLevelToTokens[thinkLevel];
|
|
269
|
+
translation = `--think ${thinkLevel} → --thinking-budget ${thinkingBudget}`;
|
|
270
|
+
if (argv.verbose) {
|
|
271
|
+
await log(`📊 Translating for Claude Code ${version} (>= ${minVersion}):`, { verbose: true });
|
|
272
|
+
await log(` ${translation}`, { verbose: true });
|
|
273
|
+
if (maxBudget !== DEFAULT_MAX_THINKING_BUDGET) {
|
|
274
|
+
await log(` Using custom --max-thinking-budget: ${maxBudget}`, { verbose: true });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
// Claude Code < 2.1.12: translate --thinking-budget to --think keywords
|
|
280
|
+
if (thinkingBudget !== undefined && thinkLevel === undefined) {
|
|
281
|
+
thinkLevel = tokensToThinkingLevel(thinkingBudget);
|
|
282
|
+
translation = `--thinking-budget ${thinkingBudget} → --think ${thinkLevel}`;
|
|
283
|
+
if (argv.verbose) {
|
|
284
|
+
await log(`📊 Translating for Claude Code ${version} (< ${minVersion}):`, { verbose: true });
|
|
285
|
+
await log(` ${translation}`, { verbose: true });
|
|
286
|
+
}
|
|
287
|
+
// Clear thinkingBudget since old versions don't support it
|
|
288
|
+
thinkingBudget = undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { thinkingBudget, thinkLevel, translation, isNewVersion, maxBudget };
|
|
293
|
+
};
|
|
222
294
|
/**
|
|
223
295
|
* Check if Playwright MCP is available and connected to Claude
|
|
224
296
|
* @returns {Promise<boolean>} True if Playwright MCP is available, false otherwise
|
|
@@ -805,8 +877,20 @@ export const executeClaudeCommand = async params => {
|
|
|
805
877
|
await log('', { verbose: true });
|
|
806
878
|
}
|
|
807
879
|
try {
|
|
808
|
-
|
|
880
|
+
// Resolve thinking settings (handles translation between --think and --thinking-budget based on Claude version)
|
|
881
|
+
// See issue #1146 for details on thinking budget translation
|
|
882
|
+
const { thinkingBudget: resolvedThinkingBudget, thinkLevel, isNewVersion } = await resolveThinkingSettings(argv, log);
|
|
883
|
+
|
|
884
|
+
// Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (see issue #1076) and optionally MAX_THINKING_TOKENS (see issue #1146)
|
|
885
|
+
const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget });
|
|
809
886
|
if (argv.verbose) await log(`📊 CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${claudeCode.maxOutputTokens}`, { verbose: true });
|
|
887
|
+
if (resolvedThinkingBudget !== undefined) {
|
|
888
|
+
await log(`📊 MAX_THINKING_TOKENS: ${resolvedThinkingBudget}`, { verbose: true });
|
|
889
|
+
}
|
|
890
|
+
// Log thinking level for older Claude Code versions that use thinking keywords
|
|
891
|
+
if (!isNewVersion && thinkLevel) {
|
|
892
|
+
await log(`📊 Thinking level (via keywords): ${thinkLevel}`, { verbose: true });
|
|
893
|
+
}
|
|
810
894
|
if (argv.resume) {
|
|
811
895
|
// When resuming, pass prompt directly with -p flag. Escape double quotes for shell.
|
|
812
896
|
const simpleEscapedPrompt = prompt.replace(/"/g, '\\"');
|
|
@@ -1351,4 +1435,7 @@ export default {
|
|
|
1351
1435
|
executeClaudeCommand,
|
|
1352
1436
|
checkForUncommittedChanges,
|
|
1353
1437
|
calculateSessionTokens,
|
|
1438
|
+
getClaudeVersion,
|
|
1439
|
+
setClaudeVersion,
|
|
1440
|
+
resolveThinkingSettings,
|
|
1354
1441
|
};
|
|
@@ -63,7 +63,10 @@ export const buildUserPrompt = params => {
|
|
|
63
63
|
promptLines.push('');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Note: --think keywords are deprecated for Claude Code >= 2.1.12
|
|
67
|
+
// Thinking is now enabled by default with 31,999 token budget
|
|
68
|
+
// Use --thinking-budget to control MAX_THINKING_TOKENS instead
|
|
69
|
+
// Keeping keywords for backward compatibility with older Claude Code versions
|
|
67
70
|
if (argv && argv.think) {
|
|
68
71
|
const thinkMessages = {
|
|
69
72
|
low: 'Think.',
|
|
@@ -89,7 +92,10 @@ export const buildUserPrompt = params => {
|
|
|
89
92
|
export const buildSystemPrompt = params => {
|
|
90
93
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
91
94
|
|
|
92
|
-
//
|
|
95
|
+
// Note: --think keywords are deprecated for Claude Code >= 2.1.12
|
|
96
|
+
// Thinking is now enabled by default with 31,999 token budget
|
|
97
|
+
// Use --thinking-budget to control MAX_THINKING_TOKENS instead
|
|
98
|
+
// Keeping keywords for backward compatibility with older Claude Code versions
|
|
93
99
|
let thinkLine = '';
|
|
94
100
|
if (argv && argv.think) {
|
|
95
101
|
const thinkMessages = {
|
package/src/config.lib.mjs
CHANGED
|
@@ -20,6 +20,9 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
20
20
|
|
|
21
21
|
const getenv = await use('getenv');
|
|
22
22
|
|
|
23
|
+
// Use semver package for version comparison (see issue #1146)
|
|
24
|
+
import semver from 'semver';
|
|
25
|
+
|
|
23
26
|
// Import lino for parsing Links Notation format
|
|
24
27
|
const { lino } = await import('./lino.lib.mjs');
|
|
25
28
|
|
|
@@ -89,8 +92,80 @@ export const claudeCode = {
|
|
|
89
92
|
maxOutputTokens: parseIntWithDefault('CLAUDE_CODE_MAX_OUTPUT_TOKENS', parseIntWithDefault('HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS', 64000)),
|
|
90
93
|
};
|
|
91
94
|
|
|
95
|
+
// Default max thinking budget for Claude Code (see issue #1146)
|
|
96
|
+
// This is the default value used by Claude Code when extended thinking is enabled
|
|
97
|
+
// Can be overridden via --max-thinking-budget option
|
|
98
|
+
export const DEFAULT_MAX_THINKING_BUDGET = 31999;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get thinking level token values calculated from max budget
|
|
102
|
+
* Values are evenly distributed: off=0, low=max/4, medium=max/2, high=max*3/4, max=max
|
|
103
|
+
* @param {number} maxBudget - Maximum thinking budget (default: 31999)
|
|
104
|
+
* @returns {Object} Mapping of thinking levels to token values
|
|
105
|
+
*/
|
|
106
|
+
export const getThinkingLevelToTokens = (maxBudget = DEFAULT_MAX_THINKING_BUDGET) => ({
|
|
107
|
+
off: 0,
|
|
108
|
+
low: Math.floor(maxBudget / 4), // ~8000 for default 31999
|
|
109
|
+
medium: Math.floor(maxBudget / 2), // ~16000 for default 31999
|
|
110
|
+
high: Math.floor((maxBudget * 3) / 4), // ~24000 for default 31999
|
|
111
|
+
max: maxBudget, // 31999 by default
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Default thinking level to tokens mapping (using default max budget)
|
|
115
|
+
export const thinkingLevelToTokens = getThinkingLevelToTokens(DEFAULT_MAX_THINKING_BUDGET);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get tokens to thinking level mapping function with configurable max budget
|
|
119
|
+
* Uses midpoint ranges to determine the level
|
|
120
|
+
* @param {number} maxBudget - Maximum thinking budget (default: 31999)
|
|
121
|
+
* @returns {Function} Function that converts tokens to thinking level
|
|
122
|
+
*/
|
|
123
|
+
export const getTokensToThinkingLevel = (maxBudget = DEFAULT_MAX_THINKING_BUDGET) => {
|
|
124
|
+
const levels = getThinkingLevelToTokens(maxBudget);
|
|
125
|
+
// Calculate midpoints between levels for range determination
|
|
126
|
+
const lowMediumMidpoint = Math.floor((levels.low + levels.medium) / 2);
|
|
127
|
+
const mediumHighMidpoint = Math.floor((levels.medium + levels.high) / 2);
|
|
128
|
+
const highMaxMidpoint = Math.floor((levels.high + levels.max) / 2);
|
|
129
|
+
|
|
130
|
+
return tokens => {
|
|
131
|
+
if (tokens === 0) return 'off';
|
|
132
|
+
if (tokens <= lowMediumMidpoint) return 'low';
|
|
133
|
+
if (tokens <= mediumHighMidpoint) return 'medium';
|
|
134
|
+
if (tokens <= highMaxMidpoint) return 'high';
|
|
135
|
+
return 'max';
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Default tokens to thinking level function (using default max budget)
|
|
140
|
+
export const tokensToThinkingLevel = getTokensToThinkingLevel(DEFAULT_MAX_THINKING_BUDGET);
|
|
141
|
+
|
|
142
|
+
// Check if a version supports thinking budget (>= minimum version)
|
|
143
|
+
// Uses semver npm package for reliable version comparison (see issue #1146)
|
|
144
|
+
export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
|
|
145
|
+
// Clean the version string (remove any leading 'v' and extra text)
|
|
146
|
+
const cleanVersion = semver.clean(version) || semver.coerce(version)?.version;
|
|
147
|
+
const cleanMinVersion = semver.clean(minVersion) || semver.coerce(minVersion)?.version;
|
|
148
|
+
|
|
149
|
+
if (!cleanVersion || !cleanMinVersion) {
|
|
150
|
+
// If versions can't be parsed, assume old version (doesn't support budget)
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return semver.gte(cleanVersion, cleanMinVersion);
|
|
155
|
+
};
|
|
156
|
+
|
|
92
157
|
// Helper function to get Claude CLI environment with CLAUDE_CODE_MAX_OUTPUT_TOKENS set
|
|
93
|
-
|
|
158
|
+
// Optionally sets MAX_THINKING_TOKENS when thinkingBudget is provided (see issue #1146)
|
|
159
|
+
export const getClaudeEnv = (options = {}) => {
|
|
160
|
+
const env = { ...process.env, CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(claudeCode.maxOutputTokens) };
|
|
161
|
+
// Set MAX_THINKING_TOKENS if thinkingBudget is provided
|
|
162
|
+
// This controls Claude Code's extended thinking feature (Claude Code >= 2.1.12)
|
|
163
|
+
// Default is 31999, set to 0 to disable thinking, max is 63999 for 64K output models
|
|
164
|
+
if (options.thinkingBudget !== undefined) {
|
|
165
|
+
env.MAX_THINKING_TOKENS = String(options.thinkingBudget);
|
|
166
|
+
}
|
|
167
|
+
return env;
|
|
168
|
+
};
|
|
94
169
|
|
|
95
170
|
// Cache TTL configurations (in milliseconds)
|
|
96
171
|
// The Usage API (Claude limits) has stricter rate limiting than regular APIs
|
package/src/github.lib.mjs
CHANGED
|
@@ -1319,6 +1319,7 @@ export async function handlePRNotFoundError({ prNumber, owner, repo, argv, shoul
|
|
|
1319
1319
|
if (argv.verbose) commandParts.push('--verbose');
|
|
1320
1320
|
if (argv.model && argv.model !== 'sonnet') commandParts.push('--model', argv.model);
|
|
1321
1321
|
if (argv.think) commandParts.push('--think', argv.think);
|
|
1322
|
+
if (argv.thinkingBudget !== undefined) commandParts.push('--thinking-budget', argv.thinkingBudget);
|
|
1322
1323
|
await log(` ${commandParts.join(' ')}`, { level: 'error' });
|
|
1323
1324
|
await log('', { level: 'error' });
|
|
1324
1325
|
}
|
package/src/hive.config.lib.mjs
CHANGED
|
@@ -209,10 +209,20 @@ export const createYargsConfig = yargsInstance => {
|
|
|
209
209
|
})
|
|
210
210
|
.option('think', {
|
|
211
211
|
type: 'string',
|
|
212
|
-
description: 'Thinking level
|
|
213
|
-
choices: ['low', 'medium', 'high', 'max'],
|
|
212
|
+
description: 'Thinking level for Claude. Translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, max=31999). For older versions, uses thinking keywords.',
|
|
213
|
+
choices: ['off', 'low', 'medium', 'high', 'max'],
|
|
214
214
|
default: undefined,
|
|
215
215
|
})
|
|
216
|
+
.option('thinking-budget', {
|
|
217
|
+
type: 'number',
|
|
218
|
+
description: 'Thinking token budget for Claude Code (0-63999). Controls MAX_THINKING_TOKENS. Default: 31999 (Claude default). Set to 0 to disable thinking.',
|
|
219
|
+
default: undefined,
|
|
220
|
+
})
|
|
221
|
+
.option('max-thinking-budget', {
|
|
222
|
+
type: 'number',
|
|
223
|
+
description: 'Maximum thinking budget for calculating --think level mappings (default: 31999 for Claude Code). Values: off=0, low=max/4, medium=max/2, high=max*3/4, max=max.',
|
|
224
|
+
default: 31999,
|
|
225
|
+
})
|
|
216
226
|
.option('prompt-plan-sub-agent', {
|
|
217
227
|
type: 'boolean',
|
|
218
228
|
description: 'Encourage AI to use Plan sub-agent for initial planning (only works with --tool claude)',
|
package/src/hive.mjs
CHANGED
|
@@ -759,6 +759,8 @@ if (isDirectExecution) {
|
|
|
759
759
|
args.push(argv.autoContinue ? '--auto-continue' : '--no-auto-continue');
|
|
760
760
|
if (argv.autoResumeOnLimitReset) args.push('--auto-resume-on-limit-reset');
|
|
761
761
|
if (argv.think) args.push('--think', argv.think);
|
|
762
|
+
if (argv.thinkingBudget !== undefined) args.push('--thinking-budget', argv.thinkingBudget);
|
|
763
|
+
if (argv.maxThinkingBudget !== undefined && argv.maxThinkingBudget !== 31999) args.push('--max-thinking-budget', argv.maxThinkingBudget);
|
|
762
764
|
if (argv.promptPlanSubAgent) args.push('--prompt-plan-sub-agent');
|
|
763
765
|
if (!argv.sentry) args.push('--no-sentry');
|
|
764
766
|
if (argv.watch) args.push('--watch');
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -216,10 +216,25 @@ export const createYargsConfig = yargsInstance => {
|
|
|
216
216
|
})
|
|
217
217
|
.option('think', {
|
|
218
218
|
type: 'string',
|
|
219
|
-
description: 'Thinking level
|
|
220
|
-
choices: ['low', 'medium', 'high', 'max'],
|
|
219
|
+
description: 'Thinking level for Claude. Translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, max=31999). For older versions, uses thinking keywords.',
|
|
220
|
+
choices: ['off', 'low', 'medium', 'high', 'max'],
|
|
221
221
|
default: undefined,
|
|
222
222
|
})
|
|
223
|
+
.option('thinking-budget', {
|
|
224
|
+
type: 'number',
|
|
225
|
+
description: 'Thinking token budget for Claude Code (0-63999). Controls MAX_THINKING_TOKENS. Default: 31999 (Claude default). Set to 0 to disable thinking. For older Claude Code versions, translated back to --think level.',
|
|
226
|
+
default: undefined,
|
|
227
|
+
})
|
|
228
|
+
.option('thinking-budget-claude-minimum-version', {
|
|
229
|
+
type: 'string',
|
|
230
|
+
description: 'Minimum Claude Code version that supports --thinking-budget (MAX_THINKING_TOKENS env var). Versions below this use thinking keywords instead.',
|
|
231
|
+
default: '2.1.12',
|
|
232
|
+
})
|
|
233
|
+
.option('max-thinking-budget', {
|
|
234
|
+
type: 'number',
|
|
235
|
+
description: 'Maximum thinking budget for calculating --think level mappings (default: 31999 for Claude Code). Values: off=0, low=max/4, medium=max/2, high=max*3/4, max=max.',
|
|
236
|
+
default: 31999,
|
|
237
|
+
})
|
|
223
238
|
.option('prompt-plan-sub-agent', {
|
|
224
239
|
type: 'boolean',
|
|
225
240
|
description: 'Encourage AI to use Plan sub-agent for initial planning (only works with --tool claude)',
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* Features:
|
|
8
8
|
* - Accepts all pending repository invitations
|
|
9
9
|
* - Accepts all pending organization invitations
|
|
10
|
-
* -
|
|
10
|
+
* - Groups output by Repositories and Organizations
|
|
11
|
+
* - Provides clickable links to repositories and organizations
|
|
12
|
+
* - Real-time progress updates during processing
|
|
11
13
|
* - Error handling with detailed error messages
|
|
12
14
|
*
|
|
13
15
|
* @see https://docs.github.com/en/rest/collaborators/invitations
|
|
@@ -28,6 +30,83 @@ function escapeMarkdown(text) {
|
|
|
28
30
|
return String(text).replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&');
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Build progress message from current state
|
|
35
|
+
* @param {Object} state - Current state object
|
|
36
|
+
* @param {string[]} state.acceptedRepos - List of accepted repo names
|
|
37
|
+
* @param {string[]} state.acceptedOrgs - List of accepted org names
|
|
38
|
+
* @param {string[]} state.errors - List of errors
|
|
39
|
+
* @param {number} state.totalRepos - Total number of repo invitations
|
|
40
|
+
* @param {number} state.totalOrgs - Total number of org invitations
|
|
41
|
+
* @param {number} state.processedRepos - Number of processed repo invitations
|
|
42
|
+
* @param {number} state.processedOrgs - Number of processed org invitations
|
|
43
|
+
* @param {boolean} state.isComplete - Whether processing is complete
|
|
44
|
+
* @returns {string} Formatted message
|
|
45
|
+
*/
|
|
46
|
+
function buildProgressMessage(state) {
|
|
47
|
+
const { acceptedRepos, acceptedOrgs, errors, totalRepos, totalOrgs, processedRepos, processedOrgs, isComplete } = state;
|
|
48
|
+
|
|
49
|
+
// Calculate totals
|
|
50
|
+
const totalInvitations = totalRepos + totalOrgs;
|
|
51
|
+
const processedTotal = processedRepos + processedOrgs;
|
|
52
|
+
const acceptedTotal = acceptedRepos.length + acceptedOrgs.length;
|
|
53
|
+
|
|
54
|
+
// Build header with progress indicator
|
|
55
|
+
let message = isComplete ? '✅ *GitHub Invitations Processed*\n\n' : `🔄 *Processing GitHub Invitations* \\(${processedTotal}/${totalInvitations}\\)\n\n`;
|
|
56
|
+
|
|
57
|
+
// Show Repositories section if any
|
|
58
|
+
if (acceptedRepos.length > 0 || (!isComplete && totalRepos > 0)) {
|
|
59
|
+
message += '*Repositories:*\n';
|
|
60
|
+
for (const repoName of acceptedRepos) {
|
|
61
|
+
// Create clickable link: [owner/repo](https://github.com/owner/repo)
|
|
62
|
+
const escapedName = escapeMarkdown(repoName);
|
|
63
|
+
const escapedLink = escapeMarkdown(`https://github.com/${repoName}`);
|
|
64
|
+
message += ` • 📦 [${escapedName}](${escapedLink})\n`;
|
|
65
|
+
}
|
|
66
|
+
// Show pending indicator if still processing repos
|
|
67
|
+
if (!isComplete && processedRepos < totalRepos) {
|
|
68
|
+
const remaining = totalRepos - processedRepos;
|
|
69
|
+
message += ` • _\\.\\.\\. ${remaining} more pending_\n`;
|
|
70
|
+
}
|
|
71
|
+
message += '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Show Organizations section if any
|
|
75
|
+
if (acceptedOrgs.length > 0 || (!isComplete && totalOrgs > 0)) {
|
|
76
|
+
message += '*Organizations:*\n';
|
|
77
|
+
for (const orgName of acceptedOrgs) {
|
|
78
|
+
// Create clickable link: [org](https://github.com/org)
|
|
79
|
+
const escapedName = escapeMarkdown(orgName);
|
|
80
|
+
const escapedLink = escapeMarkdown(`https://github.com/${orgName}`);
|
|
81
|
+
message += ` • 🏢 [${escapedName}](${escapedLink})\n`;
|
|
82
|
+
}
|
|
83
|
+
// Show pending indicator if still processing orgs
|
|
84
|
+
if (!isComplete && processedOrgs < totalOrgs) {
|
|
85
|
+
const remaining = totalOrgs - processedOrgs;
|
|
86
|
+
message += ` • _\\.\\.\\. ${remaining} more pending_\n`;
|
|
87
|
+
}
|
|
88
|
+
message += '\n';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Show errors if any
|
|
92
|
+
if (errors.length > 0) {
|
|
93
|
+
message += '*Errors:*\n' + errors.map(e => ` • ${escapeMarkdown(e)}`).join('\n') + '\n\n';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Show summary
|
|
97
|
+
if (isComplete) {
|
|
98
|
+
if (acceptedTotal === 0 && errors.length === 0) {
|
|
99
|
+
message += 'No pending invitations found\\.';
|
|
100
|
+
} else if (acceptedTotal > 0 && errors.length === 0) {
|
|
101
|
+
message += `\n🎉 Successfully accepted ${acceptedTotal} invitation\\(s\\)\\!`;
|
|
102
|
+
} else if (acceptedTotal > 0 && errors.length > 0) {
|
|
103
|
+
message += `\n⚠️ Accepted ${acceptedTotal} invitation\\(s\\), ${errors.length} error\\(s\\)\\.`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return message;
|
|
108
|
+
}
|
|
109
|
+
|
|
31
110
|
/**
|
|
32
111
|
* Registers the /accept_invites command handler with the bot
|
|
33
112
|
* @param {Object} bot - The Telegraf bot instance
|
|
@@ -61,68 +140,97 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
61
140
|
reply_to_message_id: ctx.message.message_id,
|
|
62
141
|
});
|
|
63
142
|
|
|
64
|
-
const fetchingMessage = await ctx.reply('🔄 Fetching pending GitHub invitations
|
|
65
|
-
|
|
66
|
-
|
|
143
|
+
const fetchingMessage = await ctx.reply('🔄 Fetching pending GitHub invitations\\.\\.\\.', {
|
|
144
|
+
reply_to_message_id: ctx.message.message_id,
|
|
145
|
+
parse_mode: 'MarkdownV2',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// State for tracking progress
|
|
149
|
+
const state = {
|
|
150
|
+
acceptedRepos: [],
|
|
151
|
+
acceptedOrgs: [],
|
|
152
|
+
errors: [],
|
|
153
|
+
totalRepos: 0,
|
|
154
|
+
totalOrgs: 0,
|
|
155
|
+
processedRepos: 0,
|
|
156
|
+
processedOrgs: 0,
|
|
157
|
+
isComplete: false,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Helper to update the message safely
|
|
161
|
+
const updateMessage = async () => {
|
|
162
|
+
try {
|
|
163
|
+
const message = buildProgressMessage(state);
|
|
164
|
+
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'MarkdownV2' });
|
|
165
|
+
} catch (err) {
|
|
166
|
+
// Ignore "message not modified" errors
|
|
167
|
+
if (!err.message?.includes('message is not modified')) {
|
|
168
|
+
VERBOSE && console.log(`[VERBOSE] /accept-invites: Error updating message: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
67
172
|
|
|
68
173
|
try {
|
|
69
174
|
// Fetch repository invitations
|
|
70
175
|
const { stdout: repoInvJson } = await exec('gh api /user/repository_invitations 2>/dev/null || echo "[]"');
|
|
71
176
|
const repoInvitations = JSON.parse(repoInvJson.trim() || '[]');
|
|
177
|
+
state.totalRepos = repoInvitations.length;
|
|
72
178
|
VERBOSE && console.log(`[VERBOSE] Found ${repoInvitations.length} pending repo invitations`);
|
|
73
179
|
|
|
74
|
-
//
|
|
180
|
+
// Fetch organization invitations
|
|
181
|
+
const { stdout: orgMemJson } = await exec('gh api /user/memberships/orgs 2>/dev/null || echo "[]"');
|
|
182
|
+
const orgMemberships = JSON.parse(orgMemJson.trim() || '[]');
|
|
183
|
+
const pendingOrgs = orgMemberships.filter(m => m.state === 'pending');
|
|
184
|
+
state.totalOrgs = pendingOrgs.length;
|
|
185
|
+
VERBOSE && console.log(`[VERBOSE] Found ${pendingOrgs.length} pending org invitations`);
|
|
186
|
+
|
|
187
|
+
// Check if there are any invitations
|
|
188
|
+
if (state.totalRepos === 0 && state.totalOrgs === 0) {
|
|
189
|
+
state.isComplete = true;
|
|
190
|
+
await updateMessage();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Update to show we found invitations
|
|
195
|
+
await updateMessage();
|
|
196
|
+
|
|
197
|
+
// Accept each repo invitation with progress updates
|
|
75
198
|
for (const inv of repoInvitations) {
|
|
76
199
|
const repoName = inv.repository?.full_name || 'unknown';
|
|
77
200
|
try {
|
|
78
201
|
await exec(`gh api -X PATCH /user/repository_invitations/${inv.id}`);
|
|
79
|
-
|
|
202
|
+
state.acceptedRepos.push(repoName);
|
|
80
203
|
VERBOSE && console.log(`[VERBOSE] Accepted repo invitation: ${repoName}`);
|
|
81
204
|
} catch (e) {
|
|
82
|
-
errors.push(`📦 ${repoName}: ${e.message}`);
|
|
205
|
+
state.errors.push(`📦 ${repoName}: ${e.message}`);
|
|
83
206
|
VERBOSE && console.log(`[VERBOSE] Failed to accept repo invitation ${repoName}: ${e.message}`);
|
|
84
207
|
}
|
|
208
|
+
state.processedRepos++;
|
|
209
|
+
await updateMessage();
|
|
85
210
|
}
|
|
86
211
|
|
|
87
|
-
//
|
|
88
|
-
const { stdout: orgMemJson } = await exec('gh api /user/memberships/orgs 2>/dev/null || echo "[]"');
|
|
89
|
-
const orgMemberships = JSON.parse(orgMemJson.trim() || '[]');
|
|
90
|
-
const pendingOrgs = orgMemberships.filter(m => m.state === 'pending');
|
|
91
|
-
VERBOSE && console.log(`[VERBOSE] Found ${pendingOrgs.length} pending org invitations`);
|
|
92
|
-
|
|
93
|
-
// Accept each org invitation
|
|
212
|
+
// Accept each org invitation with progress updates
|
|
94
213
|
for (const membership of pendingOrgs) {
|
|
95
214
|
const orgName = membership.organization?.login || 'unknown';
|
|
96
215
|
try {
|
|
97
216
|
await exec(`gh api -X PATCH /user/memberships/orgs/${orgName} -f state=active`);
|
|
98
|
-
|
|
217
|
+
state.acceptedOrgs.push(orgName);
|
|
99
218
|
VERBOSE && console.log(`[VERBOSE] Accepted org invitation: ${orgName}`);
|
|
100
219
|
} catch (e) {
|
|
101
|
-
errors.push(`🏢 ${orgName}: ${e.message}`);
|
|
220
|
+
state.errors.push(`🏢 ${orgName}: ${e.message}`);
|
|
102
221
|
VERBOSE && console.log(`[VERBOSE] Failed to accept org invitation ${orgName}: ${e.message}`);
|
|
103
222
|
}
|
|
223
|
+
state.processedOrgs++;
|
|
224
|
+
await updateMessage();
|
|
104
225
|
}
|
|
105
226
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
message += 'No pending invitations found.';
|
|
110
|
-
} else {
|
|
111
|
-
if (accepted.length > 0) {
|
|
112
|
-
message += '*Accepted:*\n' + accepted.map(a => ` • ${escapeMarkdown(a)}`).join('\n') + '\n\n';
|
|
113
|
-
}
|
|
114
|
-
if (errors.length > 0) {
|
|
115
|
-
message += '*Errors:*\n' + errors.map(e => ` • ${escapeMarkdown(e)}`).join('\n');
|
|
116
|
-
}
|
|
117
|
-
if (accepted.length > 0 && errors.length === 0) {
|
|
118
|
-
message += `\n🎉 Successfully accepted ${accepted.length} invitation(s)!`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
|
|
227
|
+
// Final update
|
|
228
|
+
state.isComplete = true;
|
|
229
|
+
await updateMessage();
|
|
123
230
|
} catch (error) {
|
|
124
231
|
console.error('Error in /accept-invites:', error);
|
|
125
|
-
|
|
232
|
+
const escapedError = escapeMarkdown(error.message);
|
|
233
|
+
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ Error fetching invitations: ${escapedError}\n\nMake sure \`gh\` CLI is installed and authenticated\\.`, { parse_mode: 'MarkdownV2' });
|
|
126
234
|
}
|
|
127
235
|
});
|
|
128
236
|
}
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -770,7 +770,7 @@ bot.command('help', async ctx => {
|
|
|
770
770
|
message += '🔧 *Common Options:*\n';
|
|
771
771
|
message += '• `--model <model>` or `-m` - Specify AI model (sonnet, opus, haiku, haiku-3-5, haiku-3)\n';
|
|
772
772
|
message += '• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
|
|
773
|
-
message += '• `--think <level>` - Thinking level (low/medium/high/max)\n';
|
|
773
|
+
message += '• `--think <level>` - Thinking level (off/low/medium/high/max) | `--thinking-budget <num>` - Token budget (0-63999)\n';
|
|
774
774
|
message += '• `--verbose` or `-v` - Verbose output | `--attach-logs` - Attach logs to PR\n';
|
|
775
775
|
message += '\n💡 *Tip:* Many more options available. See full documentation for complete list.\n';
|
|
776
776
|
|