@link-assistant/hive-mind 1.15.2 → 1.16.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5f78253: Add Claude Opus 4.6 model support with [1m] suffix
8
+ - `opus` alias now defaults to `claude-opus-4-6` (latest and most capable Opus model)
9
+ - Added shorter version aliases: `opus-4-6`, `opus-4-5`, `sonnet-4-5`, `haiku-4-5`
10
+ - Added `claude-haiku-4-5` alias for consistency
11
+ - `[1m]` suffix enables 1 million token context window for supported models
12
+ - Opus 4.6 gets 128K max output tokens and 64K thinking budget
13
+ - Backward compatibility: `claude-opus-4-5` maps to `claude-opus-4-5-20251101`
14
+
3
15
  ## 1.15.2
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.15.2",
3
+ "version": "1.16.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Claude CLI-related utility functions
3
- // If not, fetch it (when running standalone)
2
+ // Claude CLI-related utility functions. Fetch use-m if not available.
4
3
  if (typeof globalThis.use === 'undefined') {
5
4
  globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
6
5
  }
@@ -10,14 +9,14 @@ const path = (await use('path')).default;
10
9
  // Import log from general lib
11
10
  import { log } from './lib.mjs';
12
11
  import { reportError } from './sentry.lib.mjs';
13
- import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET } from './config.lib.mjs';
12
+ import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET, getMaxOutputTokensForModel } from './config.lib.mjs';
14
13
  import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
15
14
  import { createInteractiveHandler } from './interactive-mode.lib.mjs';
16
15
  import { displayBudgetStats } from './claude.budget-stats.lib.mjs';
17
- // Import Claude command builder for generating resume commands
18
16
  import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
19
- // Import runtime switch module (extracted to maintain file line limits, see issue #1141)
20
- import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs';
17
+ import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
18
+ import { CLAUDE_MODELS as availableModels } from './model-validation.lib.mjs'; // Issue #1221
19
+ export { availableModels }; // Re-export for backward compatibility
21
20
 
22
21
  // Helper to display resume command at end of session
23
22
  const showResumeCommand = async (sessionId, tempDir, claudePath, model, log) => {
@@ -36,16 +35,21 @@ export const formatNumber = num => {
36
35
  const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
37
36
  return decimalPart !== undefined ? `${formattedInteger}.${decimalPart}` : formattedInteger;
38
37
  };
39
- // Available model configurations
40
- export const availableModels = {
41
- sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
42
- opus: 'claude-opus-4-5-20251101', // Opus 4.5
43
- haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
44
- 'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
45
- 'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
46
- };
47
38
  // Model mapping to translate aliases to full model IDs
39
+ // Supports [1m] suffix for 1 million token context (Issue #1221)
48
40
  export const mapModelToId = model => {
41
+ if (!model || typeof model !== 'string') {
42
+ return model;
43
+ }
44
+
45
+ // Check for [1m] suffix (case-insensitive)
46
+ const match = model.match(/^(.+?)\[1m\]$/i);
47
+ if (match) {
48
+ const baseModel = match[1];
49
+ const mappedBase = availableModels[baseModel] || baseModel;
50
+ return `${mappedBase}[1m]`;
51
+ }
52
+
49
53
  return availableModels[model] || model;
50
54
  };
51
55
  // Function to validate Claude CLI connection with retry logic
@@ -218,9 +222,7 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
218
222
  // Start the validation with retry logic
219
223
  return await attemptValidation();
220
224
  };
221
- // handleClaudeRuntimeSwitch is imported from ./claude.runtime-switch.lib.mjs (see issue #1141)
222
- // Re-export it for backwards compatibility
223
- export { handleClaudeRuntimeSwitch };
225
+ export { handleClaudeRuntimeSwitch }; // Re-export from ./claude.runtime-switch.lib.mjs
224
226
 
225
227
  // Store Claude Code version globally (set during validation)
226
228
  let detectedClaudeVersion = null;
@@ -913,8 +915,10 @@ export const executeClaudeCommand = async params => {
913
915
 
914
916
  // Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (see issue #1076), MAX_THINKING_TOKENS (see issue #1146),
915
917
  // and MCP timeout configurations (see issue #1066)
916
- const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget });
917
- if (argv.verbose) await log(`📊 CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${claudeCode.maxOutputTokens}`, { verbose: true });
918
+ // Pass model for model-specific max output tokens (Issue #1221)
919
+ const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: mappedModel });
920
+ const modelMaxOutputTokens = getMaxOutputTokensForModel(mappedModel);
921
+ if (argv.verbose) await log(`📊 CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${modelMaxOutputTokens}`, { verbose: true });
918
922
  if (argv.verbose) await log(`📊 MCP_TIMEOUT: ${claudeCode.mcpTimeout}ms (server startup)`, { verbose: true });
919
923
  if (argv.verbose) await log(`📊 MCP_TOOL_TIMEOUT: ${claudeCode.mcpToolTimeout}ms (tool execution)`, { verbose: true });
920
924
  if (resolvedThinkingBudget !== undefined) {
@@ -91,13 +91,18 @@ export const retryLimits = {
91
91
 
92
92
  // Claude Code CLI configurations
93
93
  // See: https://github.com/link-assistant/hive-mind/issues/1076
94
- // Claude models support up to 64K output tokens, but Claude Code CLI defaults to 32K
94
+ // Claude models support different max output tokens:
95
+ // - Opus 4.6: 128K tokens (Issue #1221)
96
+ // - Sonnet 4.5, Opus 4.5, Haiku 4.5: 64K tokens
95
97
  // Setting a higher limit allows Claude to generate longer responses without hitting the limit
96
98
  export const claudeCode = {
97
99
  // Maximum output tokens for Claude Code CLI responses
98
100
  // Default: 64000 (matches Claude Sonnet/Opus/Haiku 4.5 model capabilities)
99
101
  // Set via CLAUDE_CODE_MAX_OUTPUT_TOKENS or HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS
100
102
  maxOutputTokens: parseIntWithDefault('CLAUDE_CODE_MAX_OUTPUT_TOKENS', parseIntWithDefault('HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS', 64000)),
103
+ // Maximum output tokens for Opus 4.6 (Issue #1221)
104
+ // See: https://platform.claude.com/docs/en/about-claude/models/overview
105
+ maxOutputTokensOpus46: parseIntWithDefault('CLAUDE_CODE_MAX_OUTPUT_TOKENS_OPUS_46', parseIntWithDefault('HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS_OPUS_46', 128000)),
101
106
  // MCP (Model Context Protocol) timeout configurations
102
107
  // See: https://github.com/link-assistant/hive-mind/issues/1066
103
108
  // See: https://code.claude.com/docs/en/settings#environment-variables
@@ -114,6 +119,47 @@ export const claudeCode = {
114
119
  // Can be overridden via --max-thinking-budget option
115
120
  export const DEFAULT_MAX_THINKING_BUDGET = 31999;
116
121
 
122
+ // Default max thinking budget for Opus 4.6 (Issue #1221)
123
+ // Opus 4.6 supports higher thinking budgets due to 128K max output tokens
124
+ // Can be overridden via --max-thinking-budget option or HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46
125
+ export const DEFAULT_MAX_THINKING_BUDGET_OPUS_46 = parseIntWithDefault('HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46', 64000);
126
+
127
+ /**
128
+ * Check if a model is Opus 4.6 or later (Issue #1221)
129
+ * @param {string} model - The model name or ID
130
+ * @returns {boolean} True if the model is Opus 4.6 or later
131
+ */
132
+ export const isOpus46OrLater = model => {
133
+ if (!model) return false;
134
+ const normalizedModel = model.toLowerCase();
135
+ // Check for opus alias (which maps to 4.6) or explicit opus-4-6
136
+ return normalizedModel === 'opus' || normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
137
+ };
138
+
139
+ /**
140
+ * Get the max output tokens for a specific model (Issue #1221)
141
+ * @param {string} model - The model name or ID
142
+ * @returns {number} The max output tokens for the model
143
+ */
144
+ export const getMaxOutputTokensForModel = model => {
145
+ if (isOpus46OrLater(model)) {
146
+ return claudeCode.maxOutputTokensOpus46;
147
+ }
148
+ return claudeCode.maxOutputTokens;
149
+ };
150
+
151
+ /**
152
+ * Get the default max thinking budget for a specific model (Issue #1221)
153
+ * @param {string} model - The model name or ID
154
+ * @returns {number} The default max thinking budget for the model
155
+ */
156
+ export const getDefaultMaxThinkingBudgetForModel = model => {
157
+ if (isOpus46OrLater(model)) {
158
+ return DEFAULT_MAX_THINKING_BUDGET_OPUS_46;
159
+ }
160
+ return DEFAULT_MAX_THINKING_BUDGET;
161
+ };
162
+
117
163
  /**
118
164
  * Get thinking level token values calculated from max budget
119
165
  * Values are evenly distributed: off=0, low=max/4, medium=max/2, high=max*3/4, max=max
@@ -174,10 +220,14 @@ export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
174
220
  // Helper function to get Claude CLI environment with CLAUDE_CODE_MAX_OUTPUT_TOKENS set
175
221
  // Optionally sets MAX_THINKING_TOKENS when thinkingBudget is provided (see issue #1146)
176
222
  // Also sets MCP_TIMEOUT and MCP_TOOL_TIMEOUT for MCP tool execution (see issue #1066)
223
+ // Supports model-specific max output tokens for Opus 4.6 (Issue #1221)
177
224
  export const getClaudeEnv = (options = {}) => {
225
+ // Get max output tokens based on model (Issue #1221)
226
+ const maxOutputTokens = options.model ? getMaxOutputTokensForModel(options.model) : claudeCode.maxOutputTokens;
227
+
178
228
  const env = {
179
229
  ...process.env,
180
- CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(claudeCode.maxOutputTokens),
230
+ CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxOutputTokens),
181
231
  // MCP timeout configurations to prevent tool calls from hanging indefinitely
182
232
  // See: https://github.com/link-assistant/hive-mind/issues/1066
183
233
  // MCP_TIMEOUT: Timeout for MCP server startup
@@ -187,7 +237,7 @@ export const getClaudeEnv = (options = {}) => {
187
237
  };
188
238
  // Set MAX_THINKING_TOKENS if thinkingBudget is provided
189
239
  // This controls Claude Code's extended thinking feature (Claude Code >= 2.1.12)
190
- // Default is 31999, set to 0 to disable thinking, max is 63999 for 64K output models
240
+ // Default is 31999 (or 64000 for Opus 4.6), set to 0 to disable thinking
191
241
  if (options.thinkingBudget !== undefined) {
192
242
  env.MAX_THINKING_TOKENS = String(options.thinkingBudget);
193
243
  }
@@ -6,12 +6,23 @@
6
6
  */
7
7
 
8
8
  // Claude models (Anthropic API)
9
+ // Updated for Opus 4.6 support (Issue #1221)
9
10
  export const claudeModels = {
10
11
  sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
11
- opus: 'claude-opus-4-5-20251101', // Opus 4.5
12
+ opus: 'claude-opus-4-6', // Opus 4.6 (latest)
12
13
  haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
13
14
  'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
14
15
  'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
16
+ // Shorter version aliases (Issue #1221 - PR comment feedback)
17
+ 'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
18
+ 'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
19
+ 'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias
20
+ 'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
21
+ // Version aliases for backward compatibility (Issue #1221)
22
+ 'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6
23
+ 'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
24
+ 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5
25
+ 'claude-haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5
15
26
  };
16
27
 
17
28
  // Agent models (OpenCode API via agent CLI)
@@ -13,12 +13,24 @@ import { log } from './lib.mjs';
13
13
  // Available models for each tool
14
14
  // These are the "known good" model names that we accept
15
15
  export const CLAUDE_MODELS = {
16
- // Short aliases
16
+ // Short aliases (single word)
17
17
  sonnet: 'claude-sonnet-4-5-20250929',
18
- opus: 'claude-opus-4-5-20251101',
18
+ opus: 'claude-opus-4-6', // Updated to Opus 4.6 (Issue #1221)
19
19
  haiku: 'claude-haiku-4-5-20251001',
20
20
  'haiku-3-5': 'claude-3-5-haiku-20241022',
21
21
  'haiku-3': 'claude-3-haiku-20240307',
22
+ // Shorter version aliases (Issue #1221 - PR comment feedback)
23
+ 'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
24
+ 'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
25
+ 'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias
26
+ 'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
27
+ // Opus version aliases (Issue #1221)
28
+ 'claude-opus-4-6': 'claude-opus-4-6', // Latest Opus
29
+ 'claude-opus-4-5': 'claude-opus-4-5-20251101', // Backward compatibility alias
30
+ // Sonnet version aliases
31
+ 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
32
+ // Haiku version aliases
33
+ 'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
22
34
  // Full model IDs (also valid inputs)
23
35
  'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
24
36
  'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
@@ -27,6 +39,18 @@ export const CLAUDE_MODELS = {
27
39
  'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
28
40
  };
29
41
 
42
+ // Models that support 1M token context window via [1m] suffix (Issue #1221)
43
+ // See: https://code.claude.com/docs/en/model-config
44
+ export const MODELS_SUPPORTING_1M_CONTEXT = [
45
+ 'claude-opus-4-6',
46
+ 'claude-sonnet-4-5-20250929',
47
+ 'claude-sonnet-4-5',
48
+ 'sonnet',
49
+ 'opus',
50
+ 'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
51
+ 'sonnet-4-5', // Short alias (Issue #1221 - PR comment feedback)
52
+ ];
53
+
30
54
  export const OPENCODE_MODELS = {
31
55
  gpt4: 'openai/gpt-4',
32
56
  gpt4o: 'openai/gpt-4o',
@@ -196,11 +220,66 @@ export const findSimilarModels = (input, validModels, maxSuggestions = 3, maxDis
196
220
  return suggestions;
197
221
  };
198
222
 
223
+ /**
224
+ * Parse model name to extract base model and optional [1m] suffix
225
+ * @param {string} model - The model name (e.g., "opus[1m]", "claude-opus-4-6[1m]")
226
+ * @returns {{ baseModel: string, has1mSuffix: boolean }}
227
+ */
228
+ export const parseModelWith1mSuffix = model => {
229
+ if (!model || typeof model !== 'string') {
230
+ return { baseModel: model, has1mSuffix: false };
231
+ }
232
+
233
+ // Check for [1m] suffix (case-insensitive)
234
+ const match = model.match(/^(.+?)\[1m\]$/i);
235
+ if (match) {
236
+ return { baseModel: match[1], has1mSuffix: true };
237
+ }
238
+
239
+ return { baseModel: model, has1mSuffix: false };
240
+ };
241
+
242
+ /**
243
+ * Check if a model supports the [1m] context window
244
+ * @param {string} model - The base model name (without [1m] suffix)
245
+ * @param {string} tool - The tool name
246
+ * @returns {boolean} True if the model supports 1M context
247
+ */
248
+ export const supports1mContext = (model, tool = 'claude') => {
249
+ if (tool !== 'claude') {
250
+ return false;
251
+ }
252
+
253
+ const normalizedModel = model.toLowerCase();
254
+
255
+ // Check if the model or its mapped version supports 1M context
256
+ for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
257
+ if (supportedModel.toLowerCase() === normalizedModel) {
258
+ return true;
259
+ }
260
+ }
261
+
262
+ // Also check if the mapped model supports 1M context
263
+ const modelMap = getModelMapForTool(tool);
264
+ const matchedKey = Object.keys(modelMap).find(key => key.toLowerCase() === normalizedModel);
265
+ if (matchedKey) {
266
+ const mappedModel = modelMap[matchedKey];
267
+ for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
268
+ if (supportedModel.toLowerCase() === mappedModel.toLowerCase()) {
269
+ return true;
270
+ }
271
+ }
272
+ }
273
+
274
+ return false;
275
+ };
276
+
199
277
  /**
200
278
  * Validate a model name against the available models for a tool
201
- * @param {string} model - The model name to validate
279
+ * Supports [1m] suffix for 1 million token context (Issue #1221)
280
+ * @param {string} model - The model name to validate (e.g., "opus", "opus[1m]", "claude-opus-4-6[1m]")
202
281
  * @param {string} tool - The tool name ('claude', 'opencode', 'codex')
203
- * @returns {{ valid: boolean, message?: string, suggestions?: string[] }}
282
+ * @returns {{ valid: boolean, message?: string, suggestions?: string[], mappedModel?: string, has1mSuffix?: boolean }}
204
283
  */
205
284
  export const validateModelName = (model, tool = 'claude') => {
206
285
  if (!model || typeof model !== 'string') {
@@ -211,23 +290,47 @@ export const validateModelName = (model, tool = 'claude') => {
211
290
  };
212
291
  }
213
292
 
293
+ // Parse [1m] suffix (Issue #1221)
294
+ const { baseModel, has1mSuffix } = parseModelWith1mSuffix(model);
295
+
214
296
  const modelMap = getModelMapForTool(tool);
215
297
  const availableNames = Object.keys(modelMap);
216
298
 
217
299
  // Case-insensitive exact match
218
- const normalizedModel = model.toLowerCase();
300
+ const normalizedModel = baseModel.toLowerCase();
219
301
  const matchedKey = availableNames.find(key => key.toLowerCase() === normalizedModel);
220
302
 
221
303
  if (matchedKey) {
304
+ const mappedModel = modelMap[matchedKey];
305
+
306
+ // If [1m] suffix is present, validate it's supported
307
+ if (has1mSuffix) {
308
+ if (!supports1mContext(baseModel, tool)) {
309
+ const supportedModels = MODELS_SUPPORTING_1M_CONTEXT.filter(m => !m.includes('-')).join(', ');
310
+ return {
311
+ valid: false,
312
+ message: `Model "${baseModel}" does not support [1m] context window.\n Models supporting 1M context: ${supportedModels}`,
313
+ suggestions: [],
314
+ };
315
+ }
316
+ // Return the mapped model with [1m] suffix appended
317
+ return {
318
+ valid: true,
319
+ mappedModel: `${mappedModel}[1m]`,
320
+ has1mSuffix: true,
321
+ };
322
+ }
323
+
222
324
  return {
223
325
  valid: true,
224
- mappedModel: modelMap[matchedKey],
326
+ mappedModel,
327
+ has1mSuffix: false,
225
328
  };
226
329
  }
227
330
 
228
331
  // Model not found - provide helpful error with suggestions
229
332
  const shortNames = getAvailableModelNames(tool);
230
- const suggestions = findSimilarModels(model, shortNames);
333
+ const suggestions = findSimilarModels(baseModel, shortNames);
231
334
 
232
335
  let message = `Unrecognized model: "${model}"`;
233
336
 
@@ -237,6 +340,11 @@ export const validateModelName = (model, tool = 'claude') => {
237
340
 
238
341
  message += `\n Available models for ${tool}: ${shortNames.join(', ')}`;
239
342
 
343
+ // Add hint about [1m] suffix if available
344
+ if (tool === 'claude') {
345
+ message += `\n Tip: Use [1m] suffix for 1M context (e.g., opus[1m], sonnet[1m])`;
346
+ }
347
+
240
348
  return {
241
349
  valid: false,
242
350
  message,