@link-assistant/hive-mind 1.56.2 → 1.56.4
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/README.hi.md +1 -1
- package/README.md +12 -1
- package/README.ru.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +2 -2
- package/src/codex.lib.mjs +2 -1
- package/src/hive.mjs +2 -2
- package/src/lenv-reader.lib.mjs +41 -29
- package/src/lino.lib.mjs +21 -7
- package/src/models/index.mjs +59 -2
- package/src/solve.config.lib.mjs +2 -2
- package/src/telegram-bot.mjs +31 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.56.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2d6d405: Fix Telegram bot LINO configuration parsing for parenthesized option/value links such as `(--isolation screen)`.
|
|
8
|
+
|
|
9
|
+
## 1.56.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 86da037: Support `gpt-5.5` for the Codex tool, prefer it as the default model, accept forward-compatible `gpt-5.5-mini` and `gpt-5.5-nano` aliases, and document per-tool model and reasoning defaults.
|
|
14
|
+
|
|
3
15
|
## 1.56.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.hi.md
CHANGED
|
@@ -452,7 +452,7 @@ Aliases:
|
|
|
452
452
|
/agent /solve --tool agent के बराबर है
|
|
453
453
|
|
|
454
454
|
Tool alias examples:
|
|
455
|
-
/codex https://github.com/owner/repo/issues/123 --model gpt-5.
|
|
455
|
+
/codex https://github.com/owner/repo/issues/123 --model gpt-5.5
|
|
456
456
|
/opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
|
|
457
457
|
/agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
|
|
458
458
|
|
package/README.md
CHANGED
|
@@ -461,7 +461,7 @@ Aliases:
|
|
|
461
461
|
/agent is equivalent to /solve --tool agent
|
|
462
462
|
|
|
463
463
|
Tool alias examples:
|
|
464
|
-
/codex https://github.com/owner/repo/issues/123 --model gpt-5.
|
|
464
|
+
/codex https://github.com/owner/repo/issues/123 --model gpt-5.5
|
|
465
465
|
/opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
|
|
466
466
|
/agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
|
|
467
467
|
|
|
@@ -477,6 +477,17 @@ Free Models via Kilo Gateway (with --tool agent):
|
|
|
477
477
|
/solve https://github.com/owner/repo/issues/123 --tool agent --model kilo/deepseek-r1-free
|
|
478
478
|
```
|
|
479
479
|
|
|
480
|
+
Current tool defaults in Hive Mind:
|
|
481
|
+
|
|
482
|
+
| Tool | Default model | Default reasoning behavior |
|
|
483
|
+
| ---------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
484
|
+
| `claude` | `sonnet` | No extra thinking is requested unless you pass `--think` or `--thinking-budget` |
|
|
485
|
+
| `codex` | `gpt-5.5` preferred, with runtime fallback to local catalog | Codex runs with `reasoning_effort=none` unless you pass `--think` or `--thinking-budget` |
|
|
486
|
+
| `opencode` | `grok-code-fast-1` | No extra thinking prompt is added for the default model |
|
|
487
|
+
| `agent` | `nemotron-3-super-free` | No extra thinking prompt is added for the default model |
|
|
488
|
+
|
|
489
|
+
See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for the full per-tool defaults and reasoning mappings.
|
|
490
|
+
|
|
480
491
|
> **📖 Free Models Guide**: See [docs/FREE_MODELS.md](./docs/FREE_MODELS.md) for comprehensive information about all free models including OpenCode Zen and Kilo Gateway providers.
|
|
481
492
|
|
|
482
493
|
#### `/hive` - Run Hive Orchestration
|
package/README.ru.md
CHANGED
|
@@ -452,7 +452,7 @@ Aliases:
|
|
|
452
452
|
/agent эквивалентна /solve --tool agent
|
|
453
453
|
|
|
454
454
|
Tool alias examples:
|
|
455
|
-
/codex https://github.com/owner/repo/issues/123 --model gpt-5.
|
|
455
|
+
/codex https://github.com/owner/repo/issues/123 --model gpt-5.5
|
|
456
456
|
/opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
|
|
457
457
|
/agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
|
|
458
458
|
|
package/README.zh.md
CHANGED
|
@@ -452,7 +452,7 @@ Aliases:
|
|
|
452
452
|
/agent 等同于 /solve --tool agent
|
|
453
453
|
|
|
454
454
|
Tool alias examples:
|
|
455
|
-
/codex https://github.com/owner/repo/issues/123 --model gpt-5.
|
|
455
|
+
/codex https://github.com/owner/repo/issues/123 --model gpt-5.5
|
|
456
456
|
/opencode https://github.com/owner/repo/issues/123 --model grok-code-fast-1
|
|
457
457
|
/agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
|
|
458
458
|
|
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.4",
|
|
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-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-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
|
@@ -23,6 +23,7 @@ import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
|
23
23
|
import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
|
|
24
24
|
import { getCodexPlaywrightMcpDisableConfigArgs } from './playwright-mcp.lib.mjs';
|
|
25
25
|
import { fetchModelInfo } from './model-info.lib.mjs';
|
|
26
|
+
import { defaultModels } from './models/index.mjs';
|
|
26
27
|
import Decimal from 'decimal.js-light';
|
|
27
28
|
|
|
28
29
|
const CODEX_USAGE_FIELD_NAMES = ['input_tokens', 'cached_input_tokens', 'output_tokens', 'cache_write_tokens', 'cache_creation_input_tokens', 'reasoning_tokens', 'input_tokens_details.cached_tokens', 'input_tokens_details.cache_read_tokens', 'input_tokens_details.cache_write_tokens', 'input_tokens_details.cache_creation_tokens', 'input_tokens_details.cache_creation_input_tokens', 'output_tokens_details.reasoning_tokens'];
|
|
@@ -408,7 +409,7 @@ export const calculateCodexPricing = async (modelId, tokenUsage) => {
|
|
|
408
409
|
};
|
|
409
410
|
|
|
410
411
|
// Function to validate Codex CLI connection
|
|
411
|
-
export const validateCodexConnection = async (model =
|
|
412
|
+
export const validateCodexConnection = async (model = defaultModels.codex, verbose = false) => {
|
|
412
413
|
// Map model alias to full ID
|
|
413
414
|
const mappedModel = mapModelToId(model);
|
|
414
415
|
|
package/src/hive.mjs
CHANGED
|
@@ -86,7 +86,7 @@ if (isRunningDirectly) {
|
|
|
86
86
|
const { validateClaudeConnection } = claudeLib;
|
|
87
87
|
// Import model validation library
|
|
88
88
|
const modelValidation = await import('./models/index.mjs');
|
|
89
|
-
const { validateAndExitOnInvalidModel, defaultModels } = modelValidation;
|
|
89
|
+
const { validateAndExitOnInvalidModel, defaultModels, resolveRuntimeDefaultModel } = modelValidation;
|
|
90
90
|
const githubLib = await import('./github.lib.mjs');
|
|
91
91
|
const { checkGitHubPermissions, fetchAllIssuesWithPagination, fetchProjectIssues, isRateLimitError, batchCheckPullRequestsForIssues, parseGitHubUrl, batchCheckArchivedRepositories } = githubLib;
|
|
92
92
|
// Import YouTrack-related functions
|
|
@@ -458,7 +458,7 @@ if (isRunningDirectly) {
|
|
|
458
458
|
|
|
459
459
|
const modelExplicitlyProvided = rawArgs.includes('--model') || rawArgs.includes('-m') || rawArgs.includes('--worker-model');
|
|
460
460
|
if (argv.tool && !modelExplicitlyProvided && defaultModels[argv.tool]) {
|
|
461
|
-
argv.model =
|
|
461
|
+
argv.model = await resolveRuntimeDefaultModel(argv.tool);
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
// Validate model names EARLY (simple string check, always runs)
|
package/src/lenv-reader.lib.mjs
CHANGED
|
@@ -38,6 +38,43 @@ const LinoParser = linoModule.Parser || linoModule.default?.Parser;
|
|
|
38
38
|
|
|
39
39
|
const fs = await import('fs');
|
|
40
40
|
|
|
41
|
+
function isCliOptionToken(value) {
|
|
42
|
+
return /^--[a-zA-Z0-9][a-zA-Z0-9=_.-]*$/.test(value) || /^-[a-zA-Z]$/.test(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function collectStringValues(value, result = []) {
|
|
46
|
+
if (value && typeof value === 'object' && Array.isArray(value.values)) {
|
|
47
|
+
if (value.id !== null && value.id !== undefined) {
|
|
48
|
+
result.push(String(value.id));
|
|
49
|
+
}
|
|
50
|
+
for (const child of value.values) {
|
|
51
|
+
collectStringValues(child, result);
|
|
52
|
+
}
|
|
53
|
+
} else if (value !== null && value !== undefined) {
|
|
54
|
+
result.push(String(value));
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateNoBareSameLineOptions(content) {
|
|
60
|
+
let currentVar = 'configuration';
|
|
61
|
+
|
|
62
|
+
for (const line of content.split(/\r?\n/)) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed || trimmed === '(' || trimmed === ')') continue;
|
|
65
|
+
|
|
66
|
+
const topLevelMatch = !/^\s/.test(line) ? trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*):(?:\s*(.*))?$/) : null;
|
|
67
|
+
const valueText = topLevelMatch ? (topLevelMatch[2] || '').trim() : trimmed;
|
|
68
|
+
if (topLevelMatch) currentVar = topLevelMatch[1];
|
|
69
|
+
if (!valueText || valueText === '(' || valueText === ')' || valueText.startsWith('(')) continue;
|
|
70
|
+
|
|
71
|
+
const parts = valueText.split(/\s+/).filter(Boolean);
|
|
72
|
+
if (parts.length > 1 && isCliOptionToken(parts[0])) {
|
|
73
|
+
throw new Error(`Invalid LINO format in "${currentVar}": Multiple values on the same line are not supported.\n` + `Found: "${parts.join(' ')}"\n` + `Each value must be on its own line with proper indentation, or grouped explicitly as a parenthesized link.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
41
78
|
/**
|
|
42
79
|
* LenvReader - Reads and parses .lenv files using LINO notation
|
|
43
80
|
*/
|
|
@@ -59,6 +96,8 @@ export class LenvReader {
|
|
|
59
96
|
const result = {};
|
|
60
97
|
|
|
61
98
|
try {
|
|
99
|
+
validateNoBareSameLineOptions(content);
|
|
100
|
+
|
|
62
101
|
// Parse the entire content as LINO
|
|
63
102
|
const parsed = this.parser.parse(content);
|
|
64
103
|
|
|
@@ -77,36 +116,9 @@ export class LenvReader {
|
|
|
77
116
|
|
|
78
117
|
// The values are the variable value
|
|
79
118
|
if (link.values && link.values.length > 0) {
|
|
80
|
-
// Check for nested structures (multiple items on same line) - reject with error
|
|
81
|
-
// A nested tuple with id=null that appears amongst other direct values indicates
|
|
82
|
-
// same-line grouping (e.g., "--option1 --option2" on same line)
|
|
83
|
-
// However, if the entire list is a SINGLE nested tuple (e.g., "VAR: (\n 1\n 2\n)"),
|
|
84
|
-
// that's valid parenthesized syntax
|
|
85
|
-
const hasDirectValues = link.values.some(v => v && typeof v === 'object' && v.id !== null);
|
|
86
|
-
const hasNestedTuples = link.values.some(v => v && typeof v === 'object' && v.id === null && v.values && v.values.length > 0);
|
|
87
|
-
|
|
88
|
-
if (hasDirectValues && hasNestedTuples) {
|
|
89
|
-
// Mixed direct values and nested tuples indicates same-line grouping
|
|
90
|
-
for (const v of link.values) {
|
|
91
|
-
if (v && typeof v === 'object' && v.id === null && v.values && v.values.length > 0) {
|
|
92
|
-
const nestedItems = v.values.map(nested => nested.id || nested).join(' ');
|
|
93
|
-
throw new Error(`Invalid LINO format in "${varName}": Multiple values on the same line are not supported.\n` + `Found: "${nestedItems}"\n` + `Each value must be on its own line with proper indentation.`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Determine which values to validate for invalid characters
|
|
99
|
-
// If it's a single nested tuple (parenthesized list), unwrap it for validation
|
|
100
|
-
let valuesToValidate = link.values;
|
|
101
|
-
if (link.values.length === 1 && link.values[0] && typeof link.values[0] === 'object' && link.values[0].id === null && link.values[0].values) {
|
|
102
|
-
// Single parenthesized list - use inner values
|
|
103
|
-
valuesToValidate = link.values[0].values;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
119
|
// Check for invalid characters in option-like values
|
|
107
|
-
for (const
|
|
120
|
+
for (const valueStr of link.values.flatMap(v => collectStringValues(v))) {
|
|
108
121
|
// Options should match pattern: --option-name or -o (with optional =value)
|
|
109
|
-
const valueStr = v.id || v;
|
|
110
122
|
if (typeof valueStr === 'string' && valueStr.startsWith('-')) {
|
|
111
123
|
// This looks like a command-line option, validate it
|
|
112
124
|
// Valid option pattern: -x, --option-name, --option-name=value
|
|
@@ -119,7 +131,7 @@ export class LenvReader {
|
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
// If there are multiple values, format them as LINO notation
|
|
122
|
-
const values = link.values.
|
|
134
|
+
const values = link.values.flatMap(v => collectStringValues(v));
|
|
123
135
|
|
|
124
136
|
// If it's a single value, just use it as-is
|
|
125
137
|
if (values.length === 1) {
|
package/src/lino.lib.mjs
CHANGED
|
@@ -9,6 +9,20 @@ const fs = await import('fs');
|
|
|
9
9
|
const path = await import('path');
|
|
10
10
|
const os = await import('os');
|
|
11
11
|
|
|
12
|
+
function collectStringValues(value, result = []) {
|
|
13
|
+
if (value && typeof value === 'object' && Array.isArray(value.values)) {
|
|
14
|
+
if (value.id !== null && value.id !== undefined) {
|
|
15
|
+
result.push(String(value.id));
|
|
16
|
+
}
|
|
17
|
+
for (const child of value.values) {
|
|
18
|
+
collectStringValues(child, result);
|
|
19
|
+
}
|
|
20
|
+
} else if (value !== null && value !== undefined) {
|
|
21
|
+
result.push(String(value));
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
export class LinksNotationManager {
|
|
13
27
|
constructor() {
|
|
14
28
|
this.parser = new LinoParser();
|
|
@@ -26,8 +40,7 @@ export class LinksNotationManager {
|
|
|
26
40
|
|
|
27
41
|
if (link.values && link.values.length > 0) {
|
|
28
42
|
for (const value of link.values) {
|
|
29
|
-
|
|
30
|
-
values.push(val);
|
|
43
|
+
values.push(...collectStringValues(value));
|
|
31
44
|
}
|
|
32
45
|
} else if (link.id) {
|
|
33
46
|
values.push(link.id);
|
|
@@ -50,9 +63,11 @@ export class LinksNotationManager {
|
|
|
50
63
|
|
|
51
64
|
if (link.values && link.values.length > 0) {
|
|
52
65
|
for (const value of link.values) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
for (const linkValue of collectStringValues(value)) {
|
|
67
|
+
const num = parseInt(linkValue);
|
|
68
|
+
if (!isNaN(num)) {
|
|
69
|
+
ids.push(num);
|
|
70
|
+
}
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
73
|
} else if (link.id) {
|
|
@@ -79,8 +94,7 @@ export class LinksNotationManager {
|
|
|
79
94
|
|
|
80
95
|
if (link.values && link.values.length > 0) {
|
|
81
96
|
for (const value of link.values) {
|
|
82
|
-
const linkStr
|
|
83
|
-
if (typeof linkStr === 'string') {
|
|
97
|
+
for (const linkStr of collectStringValues(value)) {
|
|
84
98
|
links.push(linkStr);
|
|
85
99
|
}
|
|
86
100
|
}
|
package/src/models/index.mjs
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* @see https://github.com/link-assistant/hive-mind/issues/1473
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { execFile } from 'node:child_process';
|
|
16
|
+
import { promisify } from 'node:util';
|
|
17
|
+
|
|
15
18
|
// Check if use is already defined (when imported from solve.mjs)
|
|
16
19
|
// If not, fetch it (when running standalone)
|
|
17
20
|
if (typeof globalThis.use === 'undefined') {
|
|
@@ -20,6 +23,8 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
20
23
|
|
|
21
24
|
import { log } from '../lib.mjs';
|
|
22
25
|
|
|
26
|
+
const execFileAsync = promisify(execFile);
|
|
27
|
+
|
|
23
28
|
// ─── MODEL DATA ──────────────────────────────────────────────────────────────
|
|
24
29
|
|
|
25
30
|
// Claude models (Anthropic API)
|
|
@@ -106,9 +111,13 @@ export const opencodeModels = {
|
|
|
106
111
|
export const codexModels = {
|
|
107
112
|
gpt5: 'gpt-5',
|
|
108
113
|
'gpt-5': 'gpt-5',
|
|
114
|
+
'gpt-5.5': 'gpt-5.5',
|
|
115
|
+
'gpt-5.5-mini': 'gpt-5.5-mini',
|
|
116
|
+
'gpt-5.5-nano': 'gpt-5.5-nano',
|
|
109
117
|
'gpt-5.4': 'gpt-5.4',
|
|
110
118
|
'gpt-5.4-mini': 'gpt-5.4-mini',
|
|
111
119
|
'gpt-5.4-nano': 'gpt-5.4-nano',
|
|
120
|
+
'gpt-5.2': 'gpt-5.2',
|
|
112
121
|
'gpt-5.2-codex': 'gpt-5.2-codex',
|
|
113
122
|
'gpt-5.3-codex': 'gpt-5.3-codex',
|
|
114
123
|
'gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
|
|
@@ -125,7 +134,7 @@ export const defaultModels = {
|
|
|
125
134
|
claude: 'sonnet',
|
|
126
135
|
agent: 'nemotron-3-super-free', // Issue #1563: changed from qwen3.6-plus-free (free promotion ended) per agent PR #243
|
|
127
136
|
opencode: 'grok-code-fast-1',
|
|
128
|
-
codex: 'gpt-5.
|
|
137
|
+
codex: 'gpt-5.5',
|
|
129
138
|
};
|
|
130
139
|
|
|
131
140
|
// Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238, Issue #1329)
|
|
@@ -190,9 +199,13 @@ export const OPENCODE_MODELS = {
|
|
|
190
199
|
export const CODEX_MODELS = {
|
|
191
200
|
...codexModels,
|
|
192
201
|
'gpt-5': 'gpt-5',
|
|
202
|
+
'gpt-5.5': 'gpt-5.5',
|
|
203
|
+
'gpt-5.5-mini': 'gpt-5.5-mini',
|
|
204
|
+
'gpt-5.5-nano': 'gpt-5.5-nano',
|
|
193
205
|
'gpt-5.4': 'gpt-5.4',
|
|
194
206
|
'gpt-5.4-mini': 'gpt-5.4-mini',
|
|
195
207
|
'gpt-5.4-nano': 'gpt-5.4-nano',
|
|
208
|
+
'gpt-5.2': 'gpt-5.2',
|
|
196
209
|
'gpt-5.2-codex': 'gpt-5.2-codex',
|
|
197
210
|
'gpt-5.3-codex': 'gpt-5.3-codex',
|
|
198
211
|
'gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
|
|
@@ -249,6 +262,50 @@ export const getDefaultModelForTool = tool => {
|
|
|
249
262
|
return defaultModels[tool] || defaultModels.claude;
|
|
250
263
|
};
|
|
251
264
|
|
|
265
|
+
let cachedInstalledCodexModelsPromise = null;
|
|
266
|
+
const CODEX_DEFAULT_FALLBACK_CHAIN = ['gpt-5.4', 'gpt-5.5-mini', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.2', 'gpt-5.2-codex', 'gpt-5.5-nano', 'gpt-5.4-nano'];
|
|
267
|
+
|
|
268
|
+
export const getInstalledCodexModels = async () => {
|
|
269
|
+
if (!cachedInstalledCodexModelsPromise) {
|
|
270
|
+
cachedInstalledCodexModelsPromise = (async () => {
|
|
271
|
+
try {
|
|
272
|
+
const { stdout } = await execFileAsync('codex', ['debug', 'models'], {
|
|
273
|
+
encoding: 'utf8',
|
|
274
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
275
|
+
});
|
|
276
|
+
const parsed = JSON.parse(stdout);
|
|
277
|
+
const modelSlugs = parsed?.models?.map(model => model?.slug).filter(Boolean);
|
|
278
|
+
return Array.isArray(modelSlugs) ? [...new Set(modelSlugs)] : null;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
})();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return cachedInstalledCodexModelsPromise;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const resolveRuntimeDefaultModel = async (tool, options = {}) => {
|
|
289
|
+
const toolName = (tool || 'claude').toString().toLowerCase();
|
|
290
|
+
const preferredDefault = defaultModels[toolName] || defaultModels.claude;
|
|
291
|
+
|
|
292
|
+
if (toolName !== 'codex') {
|
|
293
|
+
return preferredDefault;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const availableCodexModels = options.availableCodexModels === undefined ? await getInstalledCodexModels() : options.availableCodexModels;
|
|
297
|
+
|
|
298
|
+
if (!Array.isArray(availableCodexModels) || availableCodexModels.length === 0) {
|
|
299
|
+
return preferredDefault;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (availableCodexModels.includes(preferredDefault)) {
|
|
303
|
+
return preferredDefault;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return CODEX_DEFAULT_FALLBACK_CHAIN.find(model => availableCodexModels.includes(model)) || preferredDefault;
|
|
307
|
+
};
|
|
308
|
+
|
|
252
309
|
/**
|
|
253
310
|
* Map model name to full model ID for a specific tool
|
|
254
311
|
* @param {string} tool - The tool name (claude, agent, opencode, codex)
|
|
@@ -318,7 +375,7 @@ export const getValidModelsForTool = tool => {
|
|
|
318
375
|
export const primaryModelNames = {
|
|
319
376
|
claude: ['opus', 'sonnet', 'haiku', 'opusplan'],
|
|
320
377
|
opencode: ['grok', 'gpt4o'],
|
|
321
|
-
codex: ['gpt-5.
|
|
378
|
+
codex: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark'],
|
|
322
379
|
agent: ['nemotron-3-super-free', 'minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free'],
|
|
323
380
|
};
|
|
324
381
|
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// This approach was adopted per issue #482 feedback to minimize custom code maintenance
|
|
9
9
|
|
|
10
10
|
import { enhanceErrorMessage, detectMalformedFlags } from './option-suggestions.lib.mjs';
|
|
11
|
-
import { defaultModels, buildModelOptionDescription } from './models/index.mjs';
|
|
11
|
+
import { defaultModels, buildModelOptionDescription, resolveRuntimeDefaultModel } from './models/index.mjs';
|
|
12
12
|
import { validateBranchName } from './solve.branch.lib.mjs';
|
|
13
13
|
|
|
14
14
|
// Re-export for use by telegram-bot.mjs (avoids extra import lines there)
|
|
@@ -678,7 +678,7 @@ export const parseArguments = async (yargs, hideBin) => {
|
|
|
678
678
|
if (argv.tool && !modelExplicitlyProvided && defaultModels[argv.tool]) {
|
|
679
679
|
// User did not explicitly provide --model, so use the correct default for the tool
|
|
680
680
|
// (Issue #1473: centralized in models/index.mjs)
|
|
681
|
-
argv.model =
|
|
681
|
+
argv.model = await resolveRuntimeDefaultModel(argv.tool);
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
// Validate mutual exclusivity of --claude-file and --gitkeep-file
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.
|
|
|
28
28
|
|
|
29
29
|
// Load .env/.lenv configuration (issue #1318)
|
|
30
30
|
dotenvx.config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
|
|
31
|
-
loadLenvConfig({ override: true, quiet: true });
|
|
31
|
+
await loadLenvConfig({ override: true, quiet: true });
|
|
32
32
|
|
|
33
33
|
const yargsModule = await use('yargs@17.7.2');
|
|
34
34
|
const yargs = yargsModule.default || yargsModule;
|
|
@@ -123,7 +123,7 @@ const config = yargs(hideBin(process.argv))
|
|
|
123
123
|
|
|
124
124
|
// Configuration priority: CLI option > --configuration LINO > .lenv > .env
|
|
125
125
|
if (config.configuration) {
|
|
126
|
-
loadLenvConfig({ configuration: config.configuration, override: true, quiet: true });
|
|
126
|
+
await loadLenvConfig({ configuration: config.configuration, override: true, quiet: true });
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
const BOT_TOKEN = config.token || getenv('TELEGRAM_BOT_TOKEN', '');
|
|
@@ -173,8 +173,12 @@ if (ISOLATION_BACKEND) {
|
|
|
173
173
|
if (solveEnabled && solveOverrides.length > 0) {
|
|
174
174
|
console.log('Validating solve overrides...');
|
|
175
175
|
try {
|
|
176
|
+
const { backend: solveOverrideIsolation, filteredArgs: solveOverridesForValidation } = extractIsolationFromArgs(solveOverrides);
|
|
177
|
+
if (solveOverrideIsolation && !isValidPerCommandIsolation(solveOverrideIsolation)) {
|
|
178
|
+
throw new Error(`Invalid --isolation value '${solveOverrideIsolation}'. Must be: screen, tmux, or docker`);
|
|
179
|
+
}
|
|
176
180
|
// Add a dummy URL as the first argument (required positional for solve)
|
|
177
|
-
const testArgs = ['https://github.com/test/test/issues/1', ...
|
|
181
|
+
const testArgs = ['https://github.com/test/test/issues/1', ...solveOverridesForValidation];
|
|
178
182
|
|
|
179
183
|
// Temporarily suppress stderr to avoid yargs error output during validation
|
|
180
184
|
const originalStderrWrite = process.stderr.write;
|
|
@@ -197,7 +201,7 @@ if (solveEnabled && solveOverrides.length > 0) {
|
|
|
197
201
|
});
|
|
198
202
|
await testYargs.parse(testArgs);
|
|
199
203
|
// Issue #1482: Validate --base-branch in overrides early
|
|
200
|
-
const overrideBranchError = validateBranchInArgs(
|
|
204
|
+
const overrideBranchError = validateBranchInArgs(solveOverridesForValidation);
|
|
201
205
|
if (overrideBranchError) throw new Error(overrideBranchError);
|
|
202
206
|
console.log('✅ Solve overrides validated successfully');
|
|
203
207
|
} finally {
|
|
@@ -216,8 +220,12 @@ if (solveEnabled && solveOverrides.length > 0) {
|
|
|
216
220
|
if (hiveEnabled && hiveOverrides.length > 0) {
|
|
217
221
|
console.log('Validating hive overrides...');
|
|
218
222
|
try {
|
|
223
|
+
const { backend: hiveOverrideIsolation, filteredArgs: hiveOverridesForValidation } = extractIsolationFromArgs(hiveOverrides);
|
|
224
|
+
if (hiveOverrideIsolation && !isValidPerCommandIsolation(hiveOverrideIsolation)) {
|
|
225
|
+
throw new Error(`Invalid --isolation value '${hiveOverrideIsolation}'. Must be: screen, tmux, or docker`);
|
|
226
|
+
}
|
|
219
227
|
// Add a dummy URL as the first argument (required positional for hive)
|
|
220
|
-
const testArgs = ['https://github.com/test/test', ...
|
|
228
|
+
const testArgs = ['https://github.com/test/test', ...hiveOverridesForValidation];
|
|
221
229
|
|
|
222
230
|
// Temporarily suppress stderr to avoid yargs error output during validation
|
|
223
231
|
const originalStderrWrite = process.stderr.write;
|
|
@@ -239,7 +247,7 @@ if (hiveEnabled && hiveOverrides.length > 0) {
|
|
|
239
247
|
throw new Error(msg);
|
|
240
248
|
});
|
|
241
249
|
await testYargs.parse(testArgs);
|
|
242
|
-
const overrideBranchError = validateBranchInArgs(
|
|
250
|
+
const overrideBranchError = validateBranchInArgs(hiveOverridesForValidation); // Issue #1482
|
|
243
251
|
if (overrideBranchError) throw new Error(overrideBranchError);
|
|
244
252
|
console.log('✅ Hive overrides validated successfully');
|
|
245
253
|
} finally {
|
|
@@ -890,7 +898,13 @@ async function handleSolveCommand(ctx) {
|
|
|
890
898
|
await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(solvePerCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
891
899
|
return;
|
|
892
900
|
}
|
|
893
|
-
const
|
|
901
|
+
const mergedSolveArgs = mergeArgsWithOverrides(userArgsWithoutIsolation, solveOverrides);
|
|
902
|
+
const { backend: solveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedSolveArgs);
|
|
903
|
+
if (solveOverrideIsolation && !isValidPerCommandIsolation(solveOverrideIsolation)) {
|
|
904
|
+
await safeReply(ctx, `❌ Invalid locked --isolation value '${escapeMarkdown(solveOverrideIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const effectiveSolveIsolation = solveOverrideIsolation || solvePerCommandIsolation;
|
|
894
908
|
|
|
895
909
|
// Determine tool from args (default: claude)
|
|
896
910
|
let solveTool = 'claude';
|
|
@@ -990,9 +1004,9 @@ async function handleSolveCommand(ctx) {
|
|
|
990
1004
|
const toolQueuedCount = queueStats.queuedByTool[solveTool] || 0; // tool-specific queue count (#1551)
|
|
991
1005
|
if (check.canStart && toolQueuedCount === 0) {
|
|
992
1006
|
const startingMessage = await safeReply(ctx, `🚀 Starting solve command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
|
|
993
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock,
|
|
1007
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock, effectiveSolveIsolation);
|
|
994
1008
|
} else {
|
|
995
|
-
const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation:
|
|
1009
|
+
const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation });
|
|
996
1010
|
let queueMessage = `📋 Solve command queued (${solveTool} queue position #${toolQueuedCount + 1})\n\n${infoBlock}`; // tool-specific position (#1551)
|
|
997
1011
|
if (check.reason) queueMessage += `\n\n⏳ Waiting: ${escapeMarkdown(check.reason)}`;
|
|
998
1012
|
const queuedMessage = await safeReply(ctx, queueMessage, { reply_to_message_id: ctx.message.message_id });
|
|
@@ -1102,7 +1116,13 @@ async function handleHiveCommand(ctx) {
|
|
|
1102
1116
|
await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(hivePerCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
1103
1117
|
return;
|
|
1104
1118
|
}
|
|
1105
|
-
const
|
|
1119
|
+
const mergedHiveArgs = mergeArgsWithOverrides(normalizedArgsWithoutIsolation, hiveOverrides);
|
|
1120
|
+
const { backend: hiveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedHiveArgs);
|
|
1121
|
+
if (hiveOverrideIsolation && !isValidPerCommandIsolation(hiveOverrideIsolation)) {
|
|
1122
|
+
await safeReply(ctx, `❌ Invalid locked --isolation value '${escapeMarkdown(hiveOverrideIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const effectiveHiveIsolation = hiveOverrideIsolation || hivePerCommandIsolation;
|
|
1106
1126
|
|
|
1107
1127
|
// Determine tool from args (default: claude)
|
|
1108
1128
|
let hiveTool = 'claude';
|
|
@@ -1161,7 +1181,7 @@ async function handleHiveCommand(ctx) {
|
|
|
1161
1181
|
}
|
|
1162
1182
|
|
|
1163
1183
|
const startingMessage = await safeReply(ctx, `🚀 Starting hive command...\n\n${infoBlock}`, { reply_to_message_id: ctx.message.message_id });
|
|
1164
|
-
await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock,
|
|
1184
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock, effectiveHiveIsolation);
|
|
1165
1185
|
}
|
|
1166
1186
|
|
|
1167
1187
|
bot.command(/^hive$/i, handleHiveCommand);
|