@link-assistant/hive-mind 2.8.10 → 2.9.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 +12 -0
- package/package.json +1 -1
- package/src/codex-capability-preflight.lib.mjs +144 -15
- package/src/codex.lib.mjs +4 -4
- package/src/config.lib.mjs +25 -9
- package/src/models/index.mjs +15 -8
- package/src/solve.config.lib.mjs +2 -2
- package/src/solve.escalate.lib.mjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 42c4e91: Add full support for Claude Opus 5 (`claude-opus-5`) and make it the default model for `--tool claude` (and therefore for the `/claude` and `/solve` commands). The bare `opus` alias now resolves to `claude-opus-5` (previously `claude-opus-4-8`). Opus 5 supports 1M context (`[1m]`), the full effort ladder including `xhigh` and `max`, 128K max output tokens, and adaptive-thinking-only environment handling. Explicit `opus-5`/`claude-opus-5` aliases now correctly receive `xhigh` effort. The `opus-4-8`/`claude-opus-4-8` (and earlier) aliases are retained for backward compatibility. (Issue #2096)
|
|
8
|
+
|
|
9
|
+
## 2.8.11
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 55a318d: Keep locally provisioned `openai-curated` plugins visible when Codex's authenticated remote plugin catalog is enabled. The repository-scoped `remote_plugin` override is written in place for every TOML spelling of the setting, so an operator config that uses a dotted key or an inline table cannot produce a duplicate key that Codex refuses to load.
|
|
14
|
+
|
|
3
15
|
## 2.8.10
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -219,7 +219,7 @@ const checkModelVisibleSkills = async ({ command, env, runCommand, log, required
|
|
|
219
219
|
return { status: missing.length === 0 ? 'satisfied' : 'missing', visible, missing };
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
const skillVisibilityError = ({ missing, visible, requirements, repairs = [] }) => new CodexCapabilityPreflightError(`Codex reports the required plugins as installed, but the model cannot see: ${missing.join(', ')}. ` + `
|
|
222
|
+
const skillVisibilityError = ({ missing, visible, requirements, repairs = [] }) => new CodexCapabilityPreflightError(`Codex reports the required plugins as installed, but the model cannot see: ${missing.join(', ')}. ` + `A plugin must survive Codex loader reconciliation and have a valid payload under ` + `CODEX_HOME/plugins/cache/<marketplace>/<plugin>/<version>/skills before its skills are exposed. ` + `Visible skills were: ${visible ? [...visible].sort().join(', ') || 'none' : 'unknown'}.` + (repairs.length > 0 ? ` Attempted repairs: ${repairs.join(', ')}.` : ''), { missing, failClosed: missing.some(skill => isExplicitRequirement(requirements, skill)) });
|
|
223
223
|
|
|
224
224
|
const verifyModelVisibleSkills = async ({ command, env, runCommand, log, requiredSkills, requirements }) => {
|
|
225
225
|
const outcome = await checkModelVisibleSkills({ command, env, runCommand, log, requiredSkills });
|
|
@@ -316,10 +316,10 @@ export async function resolveRequiredPlugins(options) {
|
|
|
316
316
|
return (await resolveRequiredCapabilities(options)).plugins;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
const readIssueRequirementText = async ({ owner, repo, issueNumber, runCommand }) => {
|
|
320
|
-
const issueResult = await runCommand({ command: 'gh', args: ['api', `repos/${owner}/${repo}/issues/${issueNumber}`], env
|
|
319
|
+
const readIssueRequirementText = async ({ owner, repo, issueNumber, runCommand, env }) => {
|
|
320
|
+
const issueResult = await runCommand({ command: 'gh', args: ['api', `repos/${owner}/${repo}/issues/${issueNumber}`], env });
|
|
321
321
|
const issue = parseJsonCommand(issueResult, 'Codex capability issue discovery');
|
|
322
|
-
const commentsResult = await runCommand({ command: 'gh', args: ['api', `repos/${owner}/${repo}/issues/${issueNumber}/comments`, '--paginate'], env
|
|
322
|
+
const commentsResult = await runCommand({ command: 'gh', args: ['api', `repos/${owner}/${repo}/issues/${issueNumber}/comments`, '--paginate'], env });
|
|
323
323
|
const comments = parseJsonCommand(commentsResult, 'Codex capability comment discovery');
|
|
324
324
|
return [issue?.title, issue?.body, ...(Array.isArray(comments) ? comments.map(comment => comment?.body) : [])].filter(Boolean).join('\n');
|
|
325
325
|
};
|
|
@@ -358,6 +358,128 @@ const readIfPresent = async filePath => {
|
|
|
358
358
|
}
|
|
359
359
|
};
|
|
360
360
|
|
|
361
|
+
const escapeRegExp = value => String(value).replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
362
|
+
|
|
363
|
+
// A line inside a multi-line string is data, not structure: `[plugins."x"]`
|
|
364
|
+
// written inside `"""…"""` is a string, and treating it as a table header would
|
|
365
|
+
// splice a key into the operator's value.
|
|
366
|
+
const markStructuralLines = lines => {
|
|
367
|
+
const structural = [];
|
|
368
|
+
let openDelimiter = null;
|
|
369
|
+
for (const line of lines) {
|
|
370
|
+
structural.push(openDelimiter === null);
|
|
371
|
+
let rest = line;
|
|
372
|
+
for (;;) {
|
|
373
|
+
if (openDelimiter) {
|
|
374
|
+
const closeIndex = rest.indexOf(openDelimiter);
|
|
375
|
+
if (closeIndex === -1) break;
|
|
376
|
+
rest = rest.slice(closeIndex + openDelimiter.length);
|
|
377
|
+
openDelimiter = null;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const opened = /"""|'''/u.exec(rest);
|
|
381
|
+
if (!opened) break;
|
|
382
|
+
openDelimiter = opened[0];
|
|
383
|
+
rest = rest.slice(opened.index + opened[0].length);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return structural;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// TOML accepts the same setting as a header table, a dotted key, or an inline
|
|
390
|
+
// table. Appending `[features]` next to any of the other two is a duplicate-key
|
|
391
|
+
// document that Codex refuses to load, which would break every scoped
|
|
392
|
+
// invocation for the repository — so each spelling is edited in place.
|
|
393
|
+
export const setTomlTableBoolean = ({ config, table, key, value }) => {
|
|
394
|
+
const lines = String(config || '')
|
|
395
|
+
.replace(/\r\n/gu, '\n')
|
|
396
|
+
.split('\n');
|
|
397
|
+
const structural = markStructuralLines(lines);
|
|
398
|
+
const [escapedTable, escapedKey] = [escapeRegExp(table), escapeRegExp(key)];
|
|
399
|
+
const quoted = name => `(?:${name}|"${name}"|'${name}')`;
|
|
400
|
+
const tableHeaderPattern = new RegExp(`^\\s*\\[\\s*${quoted(escapedTable)}\\s*\\]\\s*(?:#.*)?$`, 'u');
|
|
401
|
+
const anyTableHeaderPattern = /^\s*\[\[?[^\]]*\]\]?\s*(?:#.*)?$/u;
|
|
402
|
+
// The value is everything before an optional trailing comment, so a comment
|
|
403
|
+
// an operator attached to the setting survives the rewrite.
|
|
404
|
+
const assignmentPattern = name => new RegExp(`^(\\s*${name}\\s*=\\s*)([^#]*?)(\\s*(?:#.*)?)$`, 'u');
|
|
405
|
+
const keyPattern = assignmentPattern(quoted(escapedKey));
|
|
406
|
+
const dottedPattern = assignmentPattern(`${quoted(escapedTable)}\\s*\\.\\s*${quoted(escapedKey)}`);
|
|
407
|
+
const inlineTablePattern = new RegExp(`^(\\s*${quoted(escapedTable)}\\s*=\\s*\\{)([^}]*)(\\}\\s*(?:#.*)?)$`, 'u');
|
|
408
|
+
const finish = () => `${lines.join('\n').trimEnd()}\n`;
|
|
409
|
+
const isStructural = index => structural[index];
|
|
410
|
+
|
|
411
|
+
const tableStart = lines.findIndex((line, index) => isStructural(index) && tableHeaderPattern.test(line));
|
|
412
|
+
if (tableStart !== -1) {
|
|
413
|
+
let tableEnd = lines.length;
|
|
414
|
+
for (let index = tableStart + 1; index < lines.length; index++) {
|
|
415
|
+
if (isStructural(index) && anyTableHeaderPattern.test(lines[index])) {
|
|
416
|
+
tableEnd = index;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
for (let index = tableStart + 1; index < tableEnd; index++) {
|
|
421
|
+
// Any existing value is replaced, not only a boolean literal: leaving a
|
|
422
|
+
// stale `remote_plugin = 1` behind and appending a second assignment is
|
|
423
|
+
// also a duplicate key.
|
|
424
|
+
if (isStructural(index) && keyPattern.test(lines[index])) {
|
|
425
|
+
lines[index] = lines[index].replace(keyPattern, `$1${value}$3`);
|
|
426
|
+
return finish();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Insert after the table's last content line so the key is not separated
|
|
430
|
+
// from its own table by the blank lines that precede the next one.
|
|
431
|
+
let insertAt = tableEnd;
|
|
432
|
+
while (insertAt > tableStart + 1 && lines[insertAt - 1].trim() === '') insertAt--;
|
|
433
|
+
lines.splice(insertAt, 0, `${key} = ${value}`);
|
|
434
|
+
return finish();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Outside any table header the operator may have used a dotted key or an
|
|
438
|
+
// inline table for the same setting.
|
|
439
|
+
const rootEnd = lines.findIndex((line, index) => isStructural(index) && anyTableHeaderPattern.test(line));
|
|
440
|
+
const rootLimit = rootEnd === -1 ? lines.length : rootEnd;
|
|
441
|
+
for (let index = 0; index < rootLimit; index++) {
|
|
442
|
+
if (!isStructural(index)) continue;
|
|
443
|
+
if (dottedPattern.test(lines[index])) {
|
|
444
|
+
lines[index] = lines[index].replace(dottedPattern, `$1${value}$3`);
|
|
445
|
+
return finish();
|
|
446
|
+
}
|
|
447
|
+
const inlineTable = inlineTablePattern.exec(lines[index]);
|
|
448
|
+
if (inlineTable) {
|
|
449
|
+
const [, prefix, contents, suffix] = inlineTable;
|
|
450
|
+
// The trailing separator is captured so the operator's spacing inside the
|
|
451
|
+
// braces survives the rewrite.
|
|
452
|
+
const inlineKeyPattern = new RegExp(`(^|,)(\\s*${quoted(escapedKey)}\\s*=\\s*)[^,]*?(\\s*)(,|$)`, 'u');
|
|
453
|
+
const nextContents = inlineKeyPattern.test(contents) ? contents.replace(inlineKeyPattern, `$1$2${value}$3$4`) : `${contents.trim() ? `${contents.trimEnd()}, ` : ' '}${key} = ${value} `;
|
|
454
|
+
lines[index] = `${prefix}${nextContents}${suffix}`;
|
|
455
|
+
return finish();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
while (lines.length > 0 && lines.at(-1) === '') lines.pop();
|
|
460
|
+
if (lines.length > 0) lines.push('');
|
|
461
|
+
lines.push(`[${table}]`, `${key} = ${value}`, '');
|
|
462
|
+
return lines.join('\n');
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Codex's authenticated remote global catalog owns the reserved
|
|
466
|
+
// `openai-curated` marketplace. In codex-rs 0.144.6 the prompt loader removes
|
|
467
|
+
// every locally configured plugin from that marketplace before merging remote
|
|
468
|
+
// installations. Hive provisions the local marketplace payload, so the scoped
|
|
469
|
+
// home must select that local catalog or plugin list and prompt assembly report
|
|
470
|
+
// contradictory states (issue #2094). This override is deliberately scoped to
|
|
471
|
+
// runs that selected a local curated plugin; personal marketplaces retain the
|
|
472
|
+
// operator's remote catalog setting.
|
|
473
|
+
const configureScopedPluginLoader = async ({ codexHome, plugins, log }) => {
|
|
474
|
+
const localCurated = plugins.filter(plugin => pluginIdParts(plugin).marketplace === 'openai-curated');
|
|
475
|
+
if (localCurated.length === 0) return;
|
|
476
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
477
|
+
const config = await readIfPresent(configPath);
|
|
478
|
+
const nextConfig = setTomlTableBoolean({ config, table: 'features', key: 'remote_plugin', value: false });
|
|
479
|
+
if (nextConfig !== config) await fs.writeFile(configPath, nextConfig);
|
|
480
|
+
await log(` 🧭 Scoped Codex loader: remote_plugin=false for ${localCurated.join(', ')}; an authenticated remote catalog otherwise removes local @openai-curated entries before prompt assembly`, { verbose: true });
|
|
481
|
+
};
|
|
482
|
+
|
|
361
483
|
// Runtime settings follow the operator config while plugin enablement remains
|
|
362
484
|
// persistent and isolated to this repository.
|
|
363
485
|
const syncScopedConfig = async ({ baseConfigPath, scopedConfigPath }) => {
|
|
@@ -464,13 +586,13 @@ export async function runCodexCapabilityPreflight(options = {}) {
|
|
|
464
586
|
}
|
|
465
587
|
}
|
|
466
588
|
|
|
467
|
-
async function provisionCodexCapabilities({ owner, repo, issueNumber, projectDir,
|
|
589
|
+
async function provisionCodexCapabilities({ owner, repo, issueNumber, projectDir, env = process.env, baseCodexHome = env.HIVE_MIND_PARENT_CODEX_HOME || env.CODEX_HOME || path.join(os.homedir(), '.codex'), codexPath = 'codex', runCommand = defaultRunCommand, log = async () => {} } = {}) {
|
|
468
590
|
if (!owner || !repo || !issueNumber) return { required: false, plugins: [], codexHome: null };
|
|
469
591
|
|
|
470
592
|
// `executeToolWithBun` uses a shell expression for execution. Preflight uses
|
|
471
593
|
// execFile and therefore selects the installed Codex binary directly.
|
|
472
594
|
const command = /\s/u.test(codexPath) ? 'codex' : codexPath;
|
|
473
|
-
const requirementText = await readIssueRequirementText({ owner, repo, issueNumber, runCommand });
|
|
595
|
+
const requirementText = await readIssueRequirementText({ owner, repo, issueNumber, runCommand, env });
|
|
474
596
|
const requirements = detectRequiredCodexCapabilities(requirementText);
|
|
475
597
|
for (const { capability, line } of requirements.rejected || []) {
|
|
476
598
|
await log(` ⏭️ Ignored non-capability token '${capability}' from: ${line.slice(0, 160)}`, { verbose: true });
|
|
@@ -481,8 +603,13 @@ async function provisionCodexCapabilities({ owner, repo, issueNumber, projectDir
|
|
|
481
603
|
for (const { capability, line } of requirements.evidence || []) {
|
|
482
604
|
await log(` 🔎 '${capability}' detected from: ${line.slice(0, 160)}`, { verbose: true });
|
|
483
605
|
}
|
|
484
|
-
|
|
485
|
-
|
|
606
|
+
// Every Codex-side preflight operation runs from the checkout that the
|
|
607
|
+
// solver will enter. Besides matching `codex exec`, this keeps any future
|
|
608
|
+
// repository-local discovery deterministic without relying on Hive's launch
|
|
609
|
+
// directory (issue #2094).
|
|
610
|
+
const runCodexCommand = invocation => runCommand({ ...invocation, cwd: projectDir });
|
|
611
|
+
const baseEnv = { ...env, CODEX_HOME: baseCodexHome, HIVE_MIND_PARENT_CODEX_HOME: baseCodexHome };
|
|
612
|
+
const baseCatalogResult = await runCodexCommand({ command, args: ['plugin', 'list', '--available', '--json'], env: baseEnv });
|
|
486
613
|
const baseCatalog = parseJsonCommand(baseCatalogResult, 'Codex plugin catalog discovery');
|
|
487
614
|
const skillDirectories = [path.join(os.homedir(), '.agents', 'skills'), projectDir && path.join(projectDir, '.agents', 'skills')].filter(Boolean);
|
|
488
615
|
const { plugins, providers } = await resolveRequiredCapabilities({ requirements, catalog: baseCatalog, skillDirectories });
|
|
@@ -491,24 +618,25 @@ async function provisionCodexCapabilities({ owner, repo, issueNumber, projectDir
|
|
|
491
618
|
}
|
|
492
619
|
if (plugins.length === 0) {
|
|
493
620
|
await log(' ✅ Required Agent Skills are already available from standard skill directories');
|
|
494
|
-
await verifyModelVisibleSkills({ command, env: baseEnv, runCommand, log, requiredSkills: requirements.skills, requirements });
|
|
621
|
+
await verifyModelVisibleSkills({ command, env: baseEnv, runCommand: runCodexCommand, log, requiredSkills: requirements.skills, requirements });
|
|
495
622
|
return { required: true, plugins, skills: requirements.skills, codexHome: null, baseCodexHome };
|
|
496
623
|
}
|
|
497
624
|
|
|
498
625
|
const codexHome = buildCodexCapabilityStatePath({ baseCodexHome, owner, repo });
|
|
499
626
|
await prepareScopedCodexHome({ baseCodexHome, codexHome });
|
|
500
|
-
|
|
627
|
+
await configureScopedPluginLoader({ codexHome, plugins, log });
|
|
628
|
+
const scopedEnv = { ...env, CODEX_HOME: codexHome, HIVE_MIND_PARENT_CODEX_HOME: baseCodexHome };
|
|
501
629
|
|
|
502
630
|
// Issue #2088: install *and repair*. Enablement recorded in the scoped
|
|
503
631
|
// `config.toml` survives a container restart while the payload under
|
|
504
632
|
// `plugins/cache` may not, and `codex plugin list` cannot tell those states
|
|
505
633
|
// apart — so the payload itself is the thing that gets checked and rebuilt.
|
|
506
|
-
const repair = await repairScopedPluginPayloads({ command, env: scopedEnv, runCommand, log, codexHome, baseCodexHome, plugins, providers });
|
|
634
|
+
const repair = await repairScopedPluginPayloads({ command, env: scopedEnv, runCommand: runCodexCommand, log, codexHome, baseCodexHome, plugins, providers });
|
|
507
635
|
for (const entry of repair.report) {
|
|
508
636
|
if (entry.healthy) await log(` ✅ Provisioned ${entry.pluginId} in repository-scoped Codex state`);
|
|
509
637
|
}
|
|
510
638
|
|
|
511
|
-
const verifyResult = await
|
|
639
|
+
const verifyResult = await runCodexCommand({ command, args: ['plugin', 'list', '--json'], env: scopedEnv });
|
|
512
640
|
const verifiedCatalog = parseJsonCommand(verifyResult, 'Codex capability verification');
|
|
513
641
|
const verified = new Set((verifiedCatalog.installed || []).filter(plugin => plugin.installed && plugin.enabled).map(plugin => normalizePluginSelector(plugin.pluginId)));
|
|
514
642
|
const unverified = plugins.filter(plugin => !verified.has(plugin));
|
|
@@ -525,14 +653,14 @@ async function provisionCodexCapabilities({ owner, repo, issueNumber, projectDir
|
|
|
525
653
|
// the model saw zero `superpowers:*` skills, so the run proceeded and then
|
|
526
654
|
// stalled on the repository's mandatory preflight. Confirm the requirement
|
|
527
655
|
// against the catalog the model actually receives.
|
|
528
|
-
let visibility = await checkModelVisibleSkills({ command, env: scopedEnv, runCommand, log, requiredSkills: requirements.skills });
|
|
656
|
+
let visibility = await checkModelVisibleSkills({ command, env: scopedEnv, runCommand: runCodexCommand, log, requiredSkills: requirements.skills });
|
|
529
657
|
if (visibility.status === 'missing') {
|
|
530
658
|
// The payload looks materialized but the prompt disagrees: rebuild it from
|
|
531
659
|
// scratch and re-probe before deciding (issue #2088).
|
|
532
660
|
await log(` 🛠️ Model cannot see ${visibility.missing.join(', ')}; forcing a repository-scoped plugin payload rebuild`);
|
|
533
|
-
const forced = await repairScopedPluginPayloads({ command, env: scopedEnv, runCommand, log, codexHome, baseCodexHome, plugins, providers, strategies: PLUGIN_PAYLOAD_REPAIRS.slice(1), force: true });
|
|
661
|
+
const forced = await repairScopedPluginPayloads({ command, env: scopedEnv, runCommand: runCodexCommand, log, codexHome, baseCodexHome, plugins, providers, strategies: PLUGIN_PAYLOAD_REPAIRS.slice(1), force: true });
|
|
534
662
|
repair.applied.push(...forced.applied);
|
|
535
|
-
visibility = await checkModelVisibleSkills({ command, env: scopedEnv, runCommand, log, requiredSkills: requirements.skills });
|
|
663
|
+
visibility = await checkModelVisibleSkills({ command, env: scopedEnv, runCommand: runCodexCommand, log, requiredSkills: requirements.skills });
|
|
536
664
|
}
|
|
537
665
|
if (visibility.status === 'missing') throw skillVisibilityError({ missing: visibility.missing, visible: visibility.visible, requirements, repairs: repair.applied });
|
|
538
666
|
if (visibility.status === 'unknown' && repair.unhealthy.length > 0) {
|
|
@@ -556,4 +684,5 @@ export default {
|
|
|
556
684
|
repairScopedPluginPayloads,
|
|
557
685
|
resolveRequiredCapabilities,
|
|
558
686
|
runCodexCapabilityPreflight,
|
|
687
|
+
setTomlTableBoolean,
|
|
559
688
|
};
|
package/src/codex.lib.mjs
CHANGED
|
@@ -744,8 +744,8 @@ export const executeCodex = async params => {
|
|
|
744
744
|
// Issue #1877: deploy the experimental HANDOFF.md Agent Skill so Codex loads
|
|
745
745
|
// it natively from .agents/skills/handoff/SKILL.md (no-op unless --use-handoff).
|
|
746
746
|
await deployHandoffSkill({ tempDir, argv, log, $ });
|
|
747
|
-
|
|
748
|
-
const capabilityPreflight = await runCodexCapabilityPreflight({ owner, repo, issueNumber, projectDir: tempDir, codexPath, log });
|
|
747
|
+
const codexBaseEnv = getCodexExecEnv(argv.verbose);
|
|
748
|
+
const capabilityPreflight = await runCodexCapabilityPreflight({ owner, repo, issueNumber, projectDir: tempDir, codexPath, log, env: codexBaseEnv });
|
|
749
749
|
// Execute the Codex command
|
|
750
750
|
return await executeCodexCommand({
|
|
751
751
|
tempDir,
|
|
@@ -763,7 +763,7 @@ export const executeCodex = async params => {
|
|
|
763
763
|
owner,
|
|
764
764
|
repo,
|
|
765
765
|
prNumber,
|
|
766
|
-
capabilityPreflight,
|
|
766
|
+
capabilityPreflight: { ...capabilityPreflight, codexBaseEnv },
|
|
767
767
|
});
|
|
768
768
|
};
|
|
769
769
|
|
|
@@ -809,7 +809,7 @@ export const executeCodexCommand = async params => {
|
|
|
809
809
|
const mappedModel = mapModelToId(argv.model);
|
|
810
810
|
const { reasoningEffort, source: reasoningEffortSource, rolloutTokenBudget } = resolveCodexReasoningEffort(argv);
|
|
811
811
|
const isResumeMode = !!argv.resume;
|
|
812
|
-
const codexEnv = applyCodexCapabilityEnv(getCodexExecEnv(argv.verbose), {
|
|
812
|
+
const codexEnv = applyCodexCapabilityEnv(capabilityPreflight?.codexBaseEnv || getCodexExecEnv(argv.verbose), {
|
|
813
813
|
codexHome: capabilityPreflight?.codexHome,
|
|
814
814
|
baseCodexHome: capabilityPreflight?.baseCodexHome,
|
|
815
815
|
});
|
package/src/config.lib.mjs
CHANGED
|
@@ -209,7 +209,7 @@ export const isOpus46OrLater = model => {
|
|
|
209
209
|
if (!model) return false;
|
|
210
210
|
const normalizedModel = model.toLowerCase();
|
|
211
211
|
// Check for explicit opus-4-6 or later versions, or opusplan (Issue #1223)
|
|
212
|
-
// Note: The 'opus' alias now maps to Opus
|
|
212
|
+
// Note: The 'opus' alias now maps to Opus 5 (Issue #2096), so we also check for the alias directly
|
|
213
213
|
// opusplan uses Opus for planning, so it should get Opus-level settings
|
|
214
214
|
return normalizedModel === 'opus' || normalizedModel === 'opusplan' || normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-4-8') || normalizedModel.includes('opus-5');
|
|
215
215
|
};
|
|
@@ -217,7 +217,7 @@ export const isOpus46OrLater = model => {
|
|
|
217
217
|
const isOpus47 = model => {
|
|
218
218
|
if (!model) return false;
|
|
219
219
|
const normalizedModel = model.toLowerCase();
|
|
220
|
-
// 'opus' alias now maps to Opus
|
|
220
|
+
// 'opus' alias now maps to Opus 5 (Issue #2096), which inherits 4.7/4.8 behaviour
|
|
221
221
|
// opusplan uses Opus for planning, so it gets Opus-level settings
|
|
222
222
|
return normalizedModel === 'opus' || normalizedModel === 'opusplan' || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-4-8');
|
|
223
223
|
};
|
|
@@ -246,10 +246,26 @@ export const isOpus47OrLater = model => {
|
|
|
246
246
|
export const isOpus48OrLater = model => {
|
|
247
247
|
if (!model) return false;
|
|
248
248
|
const normalizedModel = model.toLowerCase();
|
|
249
|
-
// 'opus' alias now maps to Opus
|
|
249
|
+
// 'opus' alias now maps to Opus 5 (Issue #2096)
|
|
250
250
|
return normalizedModel === 'opus' || normalizedModel === 'opusplan' || normalizedModel.includes('opus-4-8') || normalizedModel.includes('opus-5');
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Check if a model is Claude Opus 5 (Issue #2096)
|
|
255
|
+
* Opus 5 (`claude-opus-5`) is the current default for `--tool claude` (the bare
|
|
256
|
+
* `opus` alias now resolves to it). Like Opus 4.8 it supports the full effort ladder
|
|
257
|
+
* (low/medium/high/xhigh/max), up to 128k output tokens, a 1M context window, and uses
|
|
258
|
+
* adaptive thinking only (extended/manual thinking with an explicit budget is
|
|
259
|
+
* unavailable). See: https://www.anthropic.com/news/claude-opus-5
|
|
260
|
+
* @param {string} model - The model name or ID
|
|
261
|
+
* @returns {boolean} True if the model is Claude Opus 5
|
|
262
|
+
*/
|
|
263
|
+
export const isOpus5 = model => {
|
|
264
|
+
if (!model) return false;
|
|
265
|
+
const m = model.toLowerCase();
|
|
266
|
+
return m === 'opus' || m === 'opus-5' || m.includes('opus-5');
|
|
267
|
+
};
|
|
268
|
+
|
|
253
269
|
const isOpus45 = model => {
|
|
254
270
|
if (!model) return false;
|
|
255
271
|
const m = model.toLowerCase();
|
|
@@ -294,7 +310,7 @@ const isMythosPreview = model => {
|
|
|
294
310
|
* Fable 5 (`claude-fable-5`) is Anthropic's most capable widely released model
|
|
295
311
|
* (generally available June 9, 2026). It is a Mythos-class model wrapped in safety
|
|
296
312
|
* classifiers that can refuse high-risk requests (returning stop_reason "refusal")
|
|
297
|
-
* and fall back to Claude Opus
|
|
313
|
+
* and fall back to Claude Opus (the bare `opus` alias, now Opus 5).
|
|
298
314
|
* @param {string} model - The model name or ID
|
|
299
315
|
* @returns {boolean} True if the model is Claude Fable 5
|
|
300
316
|
*/
|
|
@@ -360,11 +376,11 @@ export const supportsAdaptiveThinking = model => {
|
|
|
360
376
|
/**
|
|
361
377
|
* Check if a model supports the xhigh effort level.
|
|
362
378
|
* Official docs list xhigh for Claude Fable 5, Claude Mythos 5, Claude Opus 4.7,
|
|
363
|
-
* Opus 4.8, and Sonnet 5 (Issue #1832, Issue #1875, Issue #2003).
|
|
379
|
+
* Opus 4.8, Opus 5, and Sonnet 5 (Issue #1832, Issue #1875, Issue #2003, Issue #2096).
|
|
364
380
|
* @param {string} model - The model name or ID
|
|
365
381
|
* @returns {boolean} True if the model supports xhigh effort
|
|
366
382
|
*/
|
|
367
|
-
export const supportsXHighEffortLevel = model => isFable5OrMythos5(model) || isOpus47(model) || isSonnet5(model);
|
|
383
|
+
export const supportsXHighEffortLevel = model => isFable5OrMythos5(model) || isOpus47(model) || isOpus5(model) || isSonnet5(model);
|
|
368
384
|
|
|
369
385
|
/**
|
|
370
386
|
* Check if a model supports the max effort level.
|
|
@@ -492,9 +508,9 @@ export const describeRequestedThinking = (argv = {}) => {
|
|
|
492
508
|
export const OPUS_46_EFFORT_LEVELS = ['low', 'medium', 'high', 'max'];
|
|
493
509
|
|
|
494
510
|
/**
|
|
495
|
-
* Valid effort levels for Opus 4.7
|
|
496
|
-
*
|
|
497
|
-
* Opus
|
|
511
|
+
* Valid effort levels for Opus 4.7, Opus 4.8, and Opus 5 (Issue #1620, Issue #1832, Issue #2096)
|
|
512
|
+
* These models support the additional 'xhigh' level.
|
|
513
|
+
* Opus 5 keeps the same effort level set as Opus 4.8; the default effort level is 'high'
|
|
498
514
|
* (enforced by Claude Code itself, not by this module).
|
|
499
515
|
* See: https://platform.claude.com/docs/en/build-with-claude/effort
|
|
500
516
|
* @type {string[]}
|
package/src/models/index.mjs
CHANGED
|
@@ -29,11 +29,11 @@ const execFileAsync = promisify(execFile);
|
|
|
29
29
|
// ─── MODEL DATA ──────────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
// Claude models (Anthropic API)
|
|
32
|
-
// Updated for Opus 4.5/4.6/4.7/4.8, Sonnet 4.6/5, and Fable 5 / Mythos 5 support
|
|
33
|
-
// (Issue #1221, Issue #1238, Issue #1329, Issue #1433, Issue #1620, Issue #1832, Issue #1875, Issue #2003)
|
|
32
|
+
// Updated for Opus 4.5/4.6/4.7/4.8/5, Sonnet 4.6/5, and Fable 5 / Mythos 5 support
|
|
33
|
+
// (Issue #1221, Issue #1238, Issue #1329, Issue #1433, Issue #1620, Issue #1832, Issue #1875, Issue #2003, Issue #2096)
|
|
34
34
|
export const claudeModels = {
|
|
35
|
-
sonnet: 'claude-sonnet-5', // Sonnet 5 (
|
|
36
|
-
opus: 'claude-opus-
|
|
35
|
+
sonnet: 'claude-sonnet-5', // Sonnet 5 (Issue #2003)
|
|
36
|
+
opus: 'claude-opus-5', // Opus 5 (default, Issue #2096)
|
|
37
37
|
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
38
38
|
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
39
39
|
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
@@ -48,13 +48,15 @@ export const claudeModels = {
|
|
|
48
48
|
// Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
|
|
49
49
|
'sonnet-5': 'claude-sonnet-5', // Sonnet 5 short alias (Issue #2003)
|
|
50
50
|
'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
|
|
51
|
+
'opus-5': 'claude-opus-5', // Opus 5 short alias (Issue #2096)
|
|
51
52
|
'opus-4-8': 'claude-opus-4-8', // Opus 4.8 short alias (Issue #1832)
|
|
52
53
|
'opus-4-7': 'claude-opus-4-7', // Opus 4.7 short alias (backward compatibility)
|
|
53
54
|
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias (backward compatibility)
|
|
54
55
|
'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
|
|
55
56
|
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias (backward compatibility)
|
|
56
57
|
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
57
|
-
// Version aliases for backward compatibility (Issue #1221, Issue #1329, Issue #1620, Issue #1832)
|
|
58
|
+
// Version aliases for backward compatibility (Issue #1221, Issue #1329, Issue #1620, Issue #1832, Issue #2096)
|
|
59
|
+
'claude-opus-5': 'claude-opus-5', // Opus 5 (Issue #2096)
|
|
58
60
|
'claude-opus-4-8': 'claude-opus-4-8', // Opus 4.8 (Issue #1832)
|
|
59
61
|
'claude-opus-4-7': 'claude-opus-4-7', // Opus 4.7 (backward compatibility)
|
|
60
62
|
'claude-sonnet-5': 'claude-sonnet-5', // Sonnet 5 (Issue #2003)
|
|
@@ -230,7 +232,7 @@ export const geminiModels = {
|
|
|
230
232
|
|
|
231
233
|
// Default model for each tool (Issue #1473: centralized to avoid scattered hardcoded defaults)
|
|
232
234
|
export const defaultModels = {
|
|
233
|
-
claude: 'opus', // Issue #2033: Opus is the preferred default for Claude; sonnet remains available explicitly
|
|
235
|
+
claude: 'opus', // Issue #2033: Opus is the preferred default for Claude; sonnet remains available explicitly. Opus now maps to Opus 5 (Issue #2096)
|
|
234
236
|
agent: 'nemotron-3-super-free', // Issue #1563: changed from qwen3.6-plus-free (free promotion ended) per agent PR #243
|
|
235
237
|
opencode: 'grok-code-fast-1',
|
|
236
238
|
codex: 'gpt-5.6-sol', // Issue #2027: GPT-5.6 Sol is the released Codex flagship; runtime falls back to gpt-5.5 when Sol is not in the local catalog
|
|
@@ -254,10 +256,12 @@ export const MODELS_SUPPORTING_1M_CONTEXT = [
|
|
|
254
256
|
'claude-sonnet-4-6', // Sonnet 4.6 (Issue #1329)
|
|
255
257
|
'claude-sonnet-4-5-20250929',
|
|
256
258
|
'claude-sonnet-4-5',
|
|
259
|
+
'claude-opus-5', // Opus 5 — 1M context (Issue #2096)
|
|
257
260
|
'sonnet', // Now maps to Sonnet 5 (Issue #2003)
|
|
258
261
|
'sonnet-5', // Short alias (Issue #2003)
|
|
259
262
|
'sonnet-4-6', // Short alias (Issue #1329)
|
|
260
|
-
'opus', // Now maps to Opus
|
|
263
|
+
'opus', // Now maps to Opus 5 (Issue #2096)
|
|
264
|
+
'opus-5', // Short alias (Issue #2096)
|
|
261
265
|
'opus-4-8', // Short alias (Issue #1832)
|
|
262
266
|
'opus-4-7', // Short alias (Issue #1620)
|
|
263
267
|
'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
|
|
@@ -290,6 +294,7 @@ export const CLAUDE_MODELS = {
|
|
|
290
294
|
...claudeModels,
|
|
291
295
|
'claude-fable-5': 'claude-fable-5', // Fable 5 full ID (Issue #1875)
|
|
292
296
|
'claude-mythos-5': 'claude-mythos-5', // Mythos 5 full ID (Issue #1875)
|
|
297
|
+
'claude-opus-5': 'claude-opus-5', // Opus 5 full ID (Issue #2096)
|
|
293
298
|
'claude-opus-4-8': 'claude-opus-4-8', // Opus 4.8 full ID (Issue #1832)
|
|
294
299
|
'claude-opus-4-7': 'claude-opus-4-7', // Opus 4.7 full ID (Issue #1620)
|
|
295
300
|
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
|
|
@@ -1231,12 +1236,14 @@ export const resolveModelId = (requestedModel, tool) => {
|
|
|
1231
1236
|
export const defaultFallbackModels = {
|
|
1232
1237
|
claude: {
|
|
1233
1238
|
// Claude Fable 5's safety classifiers can refuse high-risk requests and hand them
|
|
1234
|
-
// off to Claude Opus
|
|
1239
|
+
// off to Claude Opus; mirror that documented fallback here (Issue #1875).
|
|
1235
1240
|
// See: https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5
|
|
1236
1241
|
'claude-fable-5': 'opus',
|
|
1237
1242
|
// Claude Mythos 5 (limited availability) falls back to the generally available
|
|
1238
1243
|
// Mythos-class model, Claude Fable 5 (Issue #1875).
|
|
1239
1244
|
'claude-mythos-5': 'fable',
|
|
1245
|
+
// Claude Opus 5 falls back to the prior Opus generation (Issue #2096).
|
|
1246
|
+
'claude-opus-5': 'opus-4-8',
|
|
1240
1247
|
'claude-opus-4-8': 'opus-4-7',
|
|
1241
1248
|
'claude-opus-4-7': 'opus-4-6',
|
|
1242
1249
|
// Claude Sonnet 5 falls back to the prior Sonnet generation (Issue #2003).
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -354,7 +354,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
354
354
|
},
|
|
355
355
|
'fallback-model': {
|
|
356
356
|
type: 'string',
|
|
357
|
-
description: 'Fallback model to switch to on model capacity/overload errors (and, for Fable 5, on safety-classifier refusals). When supported, retries resume the same session with this model. An explicit value is pinned exactly; the built-in defaults form a chain that steps to the next-closest model on repeated capacity errors. Defaults: claude fable/claude-fable-5 -> opus (Opus
|
|
357
|
+
description: 'Fallback model to switch to on model capacity/overload errors (and, for Fable 5, on safety-classifier refusals). When supported, retries resume the same session with this model. An explicit value is pinned exactly; the built-in defaults form a chain that steps to the next-closest model on repeated capacity errors. Defaults: claude fable/claude-fable-5 -> opus (Opus 5); claude mythos-5/claude-mythos-5 -> fable; claude opus/opus-5 -> opus-4-8; claude opus-4-8 -> opus-4-7; claude opus-4-7 -> opus-4-6; codex gpt-5.6-sol -> gpt-5.6-terra -> gpt-5.6-luna -> gpt-5.5 -> gpt-5.4; all others unset.',
|
|
358
358
|
default: undefined,
|
|
359
359
|
},
|
|
360
360
|
'sub-agent-model': {
|
|
@@ -364,7 +364,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
364
364
|
},
|
|
365
365
|
'show-thinking-content': {
|
|
366
366
|
type: 'boolean',
|
|
367
|
-
description: 'Show thinking content in Claude responses. Opus 4.7+ omits thinking content by default (applies to Opus 4.8 as well); this option opts in to receive summarized thinking blocks. Disabled by default. Only affects --tool claude.',
|
|
367
|
+
description: 'Show thinking content in Claude responses. Opus 4.7+ omits thinking content by default (applies to Opus 4.8 and Opus 5 as well); this option opts in to receive summarized thinking blocks. Disabled by default. Only affects --tool claude.',
|
|
368
368
|
default: false,
|
|
369
369
|
},
|
|
370
370
|
'prompt-plan-sub-agent': {
|
|
@@ -79,10 +79,12 @@ const TIER_ALIASES = {
|
|
|
79
79
|
'claude-sonnet-4-6': 'sonnet',
|
|
80
80
|
'claude-sonnet-4-5': 'sonnet',
|
|
81
81
|
opus: 'opus',
|
|
82
|
+
'opus-5': 'opus',
|
|
82
83
|
'opus-4-8': 'opus',
|
|
83
84
|
'opus-4-7': 'opus',
|
|
84
85
|
'opus-4-6': 'opus',
|
|
85
86
|
'opus-4-5': 'opus',
|
|
87
|
+
'claude-opus-5': 'opus',
|
|
86
88
|
'claude-opus-4-8': 'opus',
|
|
87
89
|
'claude-opus-4-7': 'opus',
|
|
88
90
|
fable: 'fable',
|