thoth-agents 0.2.7 → 0.2.8

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.
@@ -3,6 +3,7 @@ import {
3
3
  CLAUDE_CODE_PROMPT_DIALECT,
4
4
  CLAUDE_CODE_SUBAGENT_NAMESPACE,
5
5
  CODEX_PROMPT_DIALECT,
6
+ CONFIRMED_OPENAI_SUBAGENT_PRESET,
6
7
  CONTEXT7_MCP_URL,
7
8
  CUSTOM_SKILLS,
8
9
  DEFAULT_MODELS,
@@ -26,6 +27,7 @@ import {
26
27
  getCustomSkillsDir,
27
28
  getExistingConfigPath,
28
29
  getExistingLiteConfigPath,
30
+ getOpenCodeManagedModelStatePath,
29
31
  getPrimaryModelId,
30
32
  getSkillRegistry,
31
33
  installCustomSkills,
@@ -35,7 +37,7 @@ import {
35
37
  renderRolePrompt,
36
38
  writeConfig,
37
39
  writeLiteConfig
38
- } from "./chunk-N7GFXGFJ.js";
40
+ } from "./chunk-YLRPDTU7.js";
39
41
 
40
42
  // src/cli/codex-paths.ts
41
43
  import { homedir } from "os";
@@ -81,6 +83,18 @@ function resolveCodexTargets(options) {
81
83
  };
82
84
  }
83
85
 
86
+ // src/cli/model-effort.ts
87
+ function normalizeEffortSelection(value) {
88
+ const normalized = value?.trim() ?? "";
89
+ if (!normalized || normalized === "inherit" || normalized === "default") {
90
+ return { kind: "inherit" };
91
+ }
92
+ return { kind: "effort", value: normalized };
93
+ }
94
+ function isExplicitEffort(selection) {
95
+ return selection.kind === "effort";
96
+ }
97
+
84
98
  // src/cli/codex-install.ts
85
99
  import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync } from "fs";
86
100
  import { basename, dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
@@ -1311,19 +1325,19 @@ function renderCodexSkillLayout(input) {
1311
1325
  }
1312
1326
 
1313
1327
  // src/harness/adapters/codex.ts
1314
- function readRootPackageVersion(context) {
1328
+ function readRootPackageVersion(context2) {
1315
1329
  const packageJsonPath = findRootPackageJsonPath([
1316
- ...hasCodexPackageRoot(context) ? [context.packageRoot] : [],
1317
- context.projectRoot,
1330
+ ...hasCodexPackageRoot(context2) ? [context2.packageRoot] : [],
1331
+ context2.projectRoot,
1318
1332
  process.cwd(),
1319
1333
  fileURLToPath(new URL(".", import.meta.url))
1320
1334
  ]);
1321
1335
  return readPackageJsonVersion(packageJsonPath);
1322
1336
  }
1323
- function createCodexPluginPackageManifest(context) {
1337
+ function createCodexPluginPackageManifest(context2) {
1324
1338
  return {
1325
1339
  name: "thoth-agents",
1326
- version: readRootPackageVersion(context),
1340
+ version: readRootPackageVersion(context2),
1327
1341
  description: "Delegate-first OpenCode plugin with seven agents, thoth-mem persistence, and bundled SDD skills."
1328
1342
  };
1329
1343
  }
@@ -1367,22 +1381,9 @@ function createCodexBuiltinMcpTomlConfig() {
1367
1381
  mcp_servers: createCodexBuiltinMcpServers()
1368
1382
  };
1369
1383
  }
1370
- var CODEX_SUBAGENT_DEFAULT_MODELS = {
1371
- oracle: "gpt-5.5",
1372
- librarian: "gpt-5.4-mini",
1373
- explorer: "gpt-5.4-mini",
1374
- designer: "gpt-5.4-mini",
1375
- quick: "gpt-5.4-mini",
1376
- deep: "gpt-5.5"
1377
- };
1378
- var CODEX_SUBAGENT_REASONING_EFFORTS = {
1379
- oracle: "high",
1380
- explorer: "low",
1381
- librarian: "medium",
1382
- designer: "medium",
1383
- quick: "low",
1384
- deep: "medium"
1385
- };
1384
+ function isCodexSubagentName(name) {
1385
+ return name in CONFIRMED_OPENAI_SUBAGENT_PRESET;
1386
+ }
1386
1387
  var CODEX_ROOT_START = "<!-- thoth-agents:codex-root:start -->";
1387
1388
  var CODEX_ROOT_END = "<!-- thoth-agents:codex-root:end -->";
1388
1389
  var CODEX_CAPABILITIES = CODEX_PROMPT_DIALECT.capabilities.capabilities;
@@ -1491,42 +1492,36 @@ function renderCodexRootInstructions(config) {
1491
1492
  "- After receiving a delegated subagent response, close that subagent session unless you will retry or intentionally keep using that exact same session; explorer and librarian sessions must always be closed immediately after their response, and retry sessions must be closed after the retry result unless explicit same-session reuse is still required.",
1492
1493
  "- Use packaged thoth-agents plugin capabilities through Codex plugin, skill, MCP, and hook review surfaces after enabling them with /plugins and /hooks.",
1493
1494
  "- For blocking user decisions in Codex Default mode, use request_user_input after features.default_mode_request_user_input is enabled; do not ask those questions in plain prose.",
1495
+ "- Whenever the root orchestrator calls `request_user_input`, it MUST NEVER set or pass `autoResolutionMs`; omit the field entirely.",
1494
1496
  "- Memory governance, role permissions, provider-per-agent controls, and hooks are instruction-level unless the active Codex runtime documents stronger enforcement.",
1495
1497
  "</codex-runtime>",
1496
1498
  CODEX_ROOT_END,
1497
1499
  ""
1498
1500
  ].join("\n");
1499
1501
  }
1500
- function agentModelReasoningEffort(role) {
1501
- return isCodexSubagentName(role.name) ? CODEX_SUBAGENT_REASONING_EFFORTS[role.name] : "medium";
1502
- }
1503
- function isCodexSubagentName(name) {
1504
- return name in CODEX_SUBAGENT_DEFAULT_MODELS;
1505
- }
1506
1502
  function getCodexAgentModel(role, config) {
1507
1503
  if (!isCodexSubagentName(role.name)) return void 0;
1508
- return getPrimaryModelId(config?.agents?.[role.name]?.model) ?? CODEX_SUBAGENT_DEFAULT_MODELS[role.name];
1504
+ return getPrimaryModelId(config?.agents?.[role.name]?.model) ?? CONFIRMED_OPENAI_SUBAGENT_PRESET[role.name].model;
1509
1505
  }
1510
- function codexSurfaceHasField(surfaceId, field) {
1511
- return getCodexSurface(surfaceId)?.fields.includes(field) ?? false;
1506
+ function getCodexAgentEffort(role, config) {
1507
+ if (!isCodexSubagentName(role.name)) return void 0;
1508
+ if (getPrimaryModelId(config?.agents?.[role.name]?.model)) return void 0;
1509
+ return CONFIRMED_OPENAI_SUBAGENT_PRESET[role.name].effort;
1512
1510
  }
1513
- function hasCodexConfig(context) {
1514
- return "config" in context;
1511
+ function hasCodexConfig(context2) {
1512
+ return "config" in context2;
1515
1513
  }
1516
- function hasCodexPackageRoot(context) {
1517
- return "packageRoot" in context && typeof context.packageRoot === "string" && context.packageRoot.length > 0;
1514
+ function hasCodexPackageRoot(context2) {
1515
+ return "packageRoot" in context2 && typeof context2.packageRoot === "string" && context2.packageRoot.length > 0;
1518
1516
  }
1519
1517
  function renderAgentArtifacts({ config }) {
1520
1518
  const artifacts = [];
1521
1519
  const diagnostics = [];
1522
- const supportsReasoningEffort = codexSurfaceHasField(
1523
- "project-agent-toml",
1524
- "model_reasoning_effort"
1525
- );
1526
1520
  for (const role of getAgentPackContract().roles.filter(
1527
1521
  (candidate) => candidate.name !== "orchestrator"
1528
1522
  )) {
1529
1523
  const model = getCodexAgentModel(role, config);
1524
+ const effort = getCodexAgentEffort(role, config);
1530
1525
  const toml = renderCodexToml({
1531
1526
  surfaceId: "project-agent-toml",
1532
1527
  values: {
@@ -1534,7 +1529,7 @@ function renderAgentArtifacts({ config }) {
1534
1529
  description: role.responsibility,
1535
1530
  developer_instructions: roleInstructions(role, config),
1536
1531
  ...model ? { model } : {},
1537
- ...supportsReasoningEffort ? { model_reasoning_effort: agentModelReasoningEffort(role) } : {},
1532
+ ...effort ? { model_reasoning_effort: effort } : {},
1538
1533
  sandbox_mode: role.canMutateWorkspace ? "workspace-write" : "read-only"
1539
1534
  }
1540
1535
  });
@@ -1621,27 +1616,27 @@ function hookReadinessDiagnostics() {
1621
1616
  }
1622
1617
  ];
1623
1618
  }
1624
- function resolveSkillOutputModes(context) {
1625
- return context.options?.codexSkillOutputModes ?? ["plugin-package"];
1619
+ function resolveSkillOutputModes(context2) {
1620
+ return context2.options?.codexSkillOutputModes ?? ["plugin-package"];
1626
1621
  }
1627
1622
  var codexAdapter = {
1628
1623
  id: "codex",
1629
1624
  displayName: "Codex",
1630
1625
  capabilities: CODEX_CAPABILITIES,
1631
- render(context) {
1632
- const config = hasCodexConfig(context) ? context.config : void 0;
1626
+ render(context2) {
1627
+ const config = hasCodexConfig(context2) ? context2.config : void 0;
1633
1628
  const agentArtifacts = renderAgentArtifacts({ config });
1634
1629
  const configArtifacts = renderConfigArtifacts();
1635
- const skillOutputModes = resolveSkillOutputModes(context);
1630
+ const skillOutputModes = resolveSkillOutputModes(context2);
1636
1631
  const skillLayout = renderCodexSkillLayout({
1637
- projectRoot: context.projectRoot,
1638
- ...hasCodexPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
1632
+ projectRoot: context2.projectRoot,
1633
+ ...hasCodexPackageRoot(context2) ? { packageRoot: context2.packageRoot } : {},
1639
1634
  skills: getSkillRegistry(),
1640
1635
  surfaceId: "plugin-skills-directory",
1641
1636
  outputModes: skillOutputModes
1642
1637
  });
1643
1638
  const pluginPackage = renderCodexPluginPackage({
1644
- manifest: createCodexPluginPackageManifest(context),
1639
+ manifest: createCodexPluginPackageManifest(context2),
1645
1640
  assets: [
1646
1641
  {
1647
1642
  surfaceId: "plugin-skills-directory",
@@ -1849,6 +1844,7 @@ import {
1849
1844
  writeFileSync as writeFileSync2
1850
1845
  } from "fs";
1851
1846
  import { dirname as dirname3 } from "path";
1847
+ import { z } from "zod";
1852
1848
  function writeTextWithBackup(path4, content) {
1853
1849
  mkdirSync2(dirname3(path4), { recursive: true });
1854
1850
  if (existsSync4(path4) && readFileSync4(path4, "utf8") === content) return false;
@@ -1871,21 +1867,37 @@ function stringRecord(value) {
1871
1867
  )
1872
1868
  );
1873
1869
  }
1870
+ var managedModelStateSchema = z.object({
1871
+ version: z.number(),
1872
+ models: z.record(z.string(), z.unknown()),
1873
+ configuredModels: z.unknown().optional(),
1874
+ configuredEfforts: z.unknown().optional()
1875
+ }).passthrough();
1876
+ function effortRecord(value) {
1877
+ return Object.fromEntries(
1878
+ Object.entries(stringRecord(value)).filter(
1879
+ ([, effort]) => isExplicitEffort(normalizeEffortSelection(effort))
1880
+ )
1881
+ );
1882
+ }
1874
1883
  function emptyManagedModelState(version) {
1875
1884
  return { version, models: {} };
1876
1885
  }
1877
1886
  function parseManagedModelStateJson(text, version) {
1878
1887
  if (!text) return emptyManagedModelState(version);
1879
1888
  try {
1880
- const parsed = JSON.parse(text);
1881
- if (parsed.version !== version || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
1889
+ const input = JSON.parse(text);
1890
+ const result = managedModelStateSchema.safeParse(input);
1891
+ if (!result.success || result.data.version !== version) {
1882
1892
  return emptyManagedModelState(version);
1883
1893
  }
1884
- const configuredModels = stringRecord(parsed.configuredModels);
1894
+ const configuredModels = stringRecord(result.data.configuredModels);
1895
+ const configuredEfforts = effortRecord(result.data.configuredEfforts);
1885
1896
  return {
1886
1897
  version,
1887
- models: stringRecord(parsed.models),
1888
- ...Object.keys(configuredModels).length > 0 ? { configuredModels } : {}
1898
+ models: stringRecord(result.data.models),
1899
+ ...Object.keys(configuredModels).length > 0 ? { configuredModels } : {},
1900
+ ...Object.keys(configuredEfforts).length > 0 ? { configuredEfforts } : {}
1889
1901
  };
1890
1902
  } catch {
1891
1903
  return emptyManagedModelState(version);
@@ -1963,6 +1975,26 @@ function replaceRoleTomlModel(content, model) {
1963
1975
  return `${rendered}
1964
1976
  ${content}`;
1965
1977
  }
1978
+ function parseRoleTomlEffort(content) {
1979
+ return /^model_reasoning_effort\s*=\s*"([^"\\]+)"\s*$/m.exec(content)?.[1];
1980
+ }
1981
+ function replaceRoleTomlEffort(content, effort) {
1982
+ const pattern = /^model_reasoning_effort\s*=\s*"[^"\\]+"\s*\r?\n?/m;
1983
+ if (effort === void 0) return content.replace(pattern, "");
1984
+ const rendered = `model_reasoning_effort = "${escapeTomlString2(effort)}"`;
1985
+ if (pattern.test(content)) return content.replace(pattern, `${rendered}
1986
+ `);
1987
+ const modelPattern = /^model\s*=\s*"(?:\\.|[^"\\])*"\s*\r?\n?/m;
1988
+ if (modelPattern.test(content)) {
1989
+ return content.replace(
1990
+ modelPattern,
1991
+ (modelLine) => `${modelLine}${rendered}
1992
+ `
1993
+ );
1994
+ }
1995
+ return `${rendered}
1996
+ ${content}`;
1997
+ }
1966
1998
  function roleManagedModelStateKey(path4) {
1967
1999
  return basename(path4);
1968
2000
  }
@@ -1998,7 +2030,8 @@ function applyCodexManagedModelOverrides(config, overrides) {
1998
2030
  const nextState = {
1999
2031
  version: MANAGED_MODEL_STATE_VERSION,
2000
2032
  models: { ...state.models },
2001
- ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {}
2033
+ ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {},
2034
+ ...state.configuredEfforts ? { configuredEfforts: { ...state.configuredEfforts } } : {}
2002
2035
  };
2003
2036
  try {
2004
2037
  for (const override of overrides) {
@@ -2011,7 +2044,12 @@ function applyCodexManagedModelOverrides(config, overrides) {
2011
2044
  );
2012
2045
  }
2013
2046
  const before = existsSync5(roleItem.targetPath) ? readFileSync5(roleItem.targetPath, "utf8") : roleItem.content;
2014
- const updated = replaceRoleTomlModel(before, override.model);
2047
+ let updated = replaceRoleTomlModel(before, override.model);
2048
+ if (override.clearEffort) {
2049
+ updated = replaceRoleTomlEffort(updated, void 0);
2050
+ } else if (override.effort !== void 0) {
2051
+ updated = replaceRoleTomlEffort(updated, override.effort);
2052
+ }
2015
2053
  if (writeTextWithBackup(roleItem.targetPath, updated)) {
2016
2054
  changed.push(roleItem.targetPath);
2017
2055
  }
@@ -2019,6 +2057,14 @@ function applyCodexManagedModelOverrides(config, overrides) {
2019
2057
  nextState.models[key] = parseRoleTomlModel(roleItem.content) ?? override.model;
2020
2058
  nextState.configuredModels ??= {};
2021
2059
  nextState.configuredModels[key] = override.model;
2060
+ if (override.clearEffort) {
2061
+ if (nextState.configuredEfforts) {
2062
+ delete nextState.configuredEfforts[key];
2063
+ }
2064
+ } else if (override.effort !== void 0) {
2065
+ nextState.configuredEfforts ??= {};
2066
+ nextState.configuredEfforts[key] = override.effort;
2067
+ }
2022
2068
  }
2023
2069
  if (writeTextWithBackup(statePath, stableJson3(nextState))) {
2024
2070
  changed.push(statePath);
@@ -2042,6 +2088,11 @@ function resolveRoleTomlContent(options) {
2042
2088
  const key = roleManagedModelStateKey(options.targetPath);
2043
2089
  if (!renderedModel) return options.renderedContent;
2044
2090
  const configuredModel = options.reset ? void 0 : options.state.configuredModels?.[key];
2091
+ const configuredEffort = options.reset ? void 0 : options.state.configuredEfforts?.[key];
2092
+ if (configuredEffort !== void 0) {
2093
+ options.nextState.configuredEfforts ??= {};
2094
+ options.nextState.configuredEfforts[key] = configuredEffort;
2095
+ }
2045
2096
  if (options.reset || !existsSync5(options.targetPath)) {
2046
2097
  options.nextState.models[key] = renderedModel;
2047
2098
  if (configuredModel !== void 0) {
@@ -2051,16 +2102,19 @@ function resolveRoleTomlContent(options) {
2051
2102
  }
2052
2103
  return options.renderedContent;
2053
2104
  }
2054
- const currentModel = parseRoleTomlModel(
2055
- readFileSync5(options.targetPath, "utf8")
2056
- );
2105
+ const installedContent = readFileSync5(options.targetPath, "utf8");
2106
+ const currentModel = parseRoleTomlModel(installedContent);
2107
+ const currentEffort = parseRoleTomlEffort(installedContent);
2057
2108
  if (configuredModel !== void 0) {
2058
2109
  options.nextState.models[key] = renderedModel;
2059
2110
  options.nextState.configuredModels ??= {};
2060
2111
  options.nextState.configuredModels[key] = configuredModel;
2061
- return replaceRoleTomlModel(
2062
- options.renderedContent,
2063
- currentModel && currentModel !== configuredModel ? currentModel : configuredModel
2112
+ return replaceRoleTomlEffort(
2113
+ replaceRoleTomlModel(
2114
+ options.renderedContent,
2115
+ currentModel && currentModel !== configuredModel ? currentModel : configuredModel
2116
+ ),
2117
+ currentEffort
2064
2118
  );
2065
2119
  }
2066
2120
  const trackedModel = options.state.models[key];
@@ -2068,10 +2122,13 @@ function resolveRoleTomlContent(options) {
2068
2122
  if (isUserOwned) {
2069
2123
  if (trackedModel !== void 0)
2070
2124
  options.nextState.models[key] = trackedModel;
2071
- return replaceRoleTomlModel(options.renderedContent, currentModel);
2125
+ return replaceRoleTomlEffort(
2126
+ replaceRoleTomlModel(options.renderedContent, currentModel),
2127
+ currentEffort
2128
+ );
2072
2129
  }
2073
2130
  options.nextState.models[key] = renderedModel;
2074
- return options.renderedContent;
2131
+ return replaceRoleTomlEffort(options.renderedContent, currentEffort);
2075
2132
  }
2076
2133
  function mergePersonalMarketplace(existing, homeDir, personalPluginRoot) {
2077
2134
  const parsed = existing.trim() ? JSON.parse(existing) : {};
@@ -2445,6 +2502,7 @@ function renderClaudeCodeSubagent(input) {
2445
2502
  `name: ${yamlScalar(input.name)}`,
2446
2503
  `description: ${yamlScalar(input.description)}`,
2447
2504
  `model: ${input.model}`,
2505
+ ...input.effort !== void 0 ? [`effort: ${input.effort}`] : [],
2448
2506
  ...input.tools !== void 0 ? [`tools: ${yamlScalar(input.tools)}`] : [],
2449
2507
  "---"
2450
2508
  ].join("\n");
@@ -2584,25 +2642,25 @@ function stableJson5(value) {
2584
2642
  return `${JSON.stringify(value, null, 2)}
2585
2643
  `;
2586
2644
  }
2587
- function readRootPackageVersion2(context) {
2645
+ function readRootPackageVersion2(context2) {
2588
2646
  const packageJsonPath = findRootPackageJsonPath([
2589
- ...hasPackageRoot(context) ? [context.packageRoot] : [],
2590
- context.projectRoot,
2647
+ ...hasPackageRoot(context2) ? [context2.packageRoot] : [],
2648
+ context2.projectRoot,
2591
2649
  process.cwd(),
2592
2650
  fileURLToPath3(new URL(".", import.meta.url))
2593
2651
  ]);
2594
2652
  return readPackageJsonVersion(packageJsonPath);
2595
2653
  }
2596
- function hasConfig(context) {
2597
- return "config" in context;
2654
+ function hasConfig(context2) {
2655
+ return "config" in context2;
2598
2656
  }
2599
- function hasPackageRoot(context) {
2600
- return "packageRoot" in context && typeof context.packageRoot === "string" && context.packageRoot.length > 0;
2657
+ function hasPackageRoot(context2) {
2658
+ return "packageRoot" in context2 && typeof context2.packageRoot === "string" && context2.packageRoot.length > 0;
2601
2659
  }
2602
- function createPluginManifest(context) {
2660
+ function createPluginManifest(context2) {
2603
2661
  return {
2604
2662
  name: "thoth-agents",
2605
- version: readRootPackageVersion2(context),
2663
+ version: readRootPackageVersion2(context2),
2606
2664
  description: "Delegate-first agent pack with seven roles, thoth-mem persistence, and bundled SDD skills, packaged for Claude Code.",
2607
2665
  author: { name: "thoth-agents" }
2608
2666
  };
@@ -2650,8 +2708,8 @@ var claudeCodeAdapter = {
2650
2708
  id: "claude",
2651
2709
  displayName: "Claude Code",
2652
2710
  capabilities: CLAUDE_CODE_CAPABILITIES,
2653
- render(context) {
2654
- const config = hasConfig(context) ? context.config : void 0;
2711
+ render(context2) {
2712
+ const config = hasConfig(context2) ? context2.config : void 0;
2655
2713
  const componentArtifacts = [
2656
2714
  ...renderSubagentArtifacts(config),
2657
2715
  renderOrchestratorArtifact(config),
@@ -2671,13 +2729,13 @@ var claudeCodeAdapter = {
2671
2729
  }
2672
2730
  ];
2673
2731
  const skillLayout = renderClaudeCodeSkillLayout({
2674
- projectRoot: context.projectRoot,
2675
- ...hasPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
2732
+ projectRoot: context2.projectRoot,
2733
+ ...hasPackageRoot(context2) ? { packageRoot: context2.packageRoot } : {},
2676
2734
  skills: getSkillRegistry()
2677
2735
  });
2678
2736
  componentArtifacts.push(...skillLayout.artifacts);
2679
2737
  const pluginPackage = renderClaudeCodePluginPackage({
2680
- manifest: createPluginManifest(context),
2738
+ manifest: createPluginManifest(context2),
2681
2739
  componentArtifacts
2682
2740
  });
2683
2741
  return {
@@ -2734,6 +2792,19 @@ function replaceSubagentModel(content, model) {
2734
2792
  }
2735
2793
  return content;
2736
2794
  }
2795
+ function parseSubagentEffort(content) {
2796
+ return /^effort:\s*(\S+)\s*$/m.exec(content)?.[1];
2797
+ }
2798
+ function replaceSubagentEffort(content, effort) {
2799
+ if (effort === void 0) {
2800
+ return content.replace(/^effort:\s*\S+\s*\n/m, "");
2801
+ }
2802
+ if (/^effort:\s*\S+\s*$/m.test(content)) {
2803
+ return content.replace(/^effort:\s*\S+\s*$/m, `effort: ${effort}`);
2804
+ }
2805
+ return content.replace(/^(model:\s*\S+\s*)$/m, `$1
2806
+ effort: ${effort}`);
2807
+ }
2737
2808
  function emptyManagedModelState3() {
2738
2809
  return emptyManagedModelState(CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION);
2739
2810
  }
@@ -2769,15 +2840,29 @@ function roleForArtifact(artifact) {
2769
2840
  const name = match?.[1];
2770
2841
  return name && CLAUDE_CODE_ROLE_NAMES.includes(name) ? name : void 0;
2771
2842
  }
2772
- function applyConfiguredModel(content, role, state, nextState, reset) {
2843
+ function applyConfiguredModel(content, targetPath, role, state, nextState, reset) {
2773
2844
  const renderedModel = parseSubagentModel(content);
2774
2845
  if (!role || !renderedModel) return content;
2775
2846
  nextState.models[role] = renderedModel;
2776
2847
  const configured = reset ? void 0 : state.configuredModels?.[role];
2777
- if (configured === void 0) return content;
2778
- nextState.configuredModels ??= {};
2779
- nextState.configuredModels[role] = configured;
2780
- return replaceSubagentModel(content, configured);
2848
+ let updated = content;
2849
+ if (configured !== void 0) {
2850
+ nextState.configuredModels ??= {};
2851
+ nextState.configuredModels[role] = configured;
2852
+ updated = replaceSubagentModel(updated, configured);
2853
+ }
2854
+ const configuredEffort = reset ? void 0 : state.configuredEfforts?.[role];
2855
+ if (configuredEffort !== void 0) {
2856
+ nextState.configuredEfforts ??= {};
2857
+ nextState.configuredEfforts[role] = configuredEffort;
2858
+ }
2859
+ if (!reset && existsSync6(targetPath)) {
2860
+ const installedEffort = parseSubagentEffort(
2861
+ readFileSync7(targetPath, "utf8")
2862
+ );
2863
+ updated = replaceSubagentEffort(updated, installedEffort);
2864
+ }
2865
+ return updated;
2781
2866
  }
2782
2867
  function buildClaudeCodeSetupPlan(config) {
2783
2868
  const targets = resolveClaudeCodeTargets({
@@ -2795,8 +2880,15 @@ function buildClaudeCodeSetupPlan(config) {
2795
2880
  const items = render.artifacts.map((artifact) => {
2796
2881
  const role = roleForArtifact(artifact);
2797
2882
  const rendered = String(artifact.content ?? "");
2798
- const content = role !== void 0 ? applyConfiguredModel(rendered, role, state, nextState, config.reset) : rendered;
2799
2883
  const targetPath = join8(targets.pluginRoot, artifact.path);
2884
+ const content = role !== void 0 ? applyConfiguredModel(
2885
+ rendered,
2886
+ targetPath,
2887
+ role,
2888
+ state,
2889
+ nextState,
2890
+ config.reset
2891
+ ) : rendered;
2800
2892
  return {
2801
2893
  kind: targetKindForArtifact(artifact),
2802
2894
  action: "write-plugin-file",
@@ -2892,11 +2984,12 @@ function applyClaudeCodeManagedModelOverrides(config, overrides) {
2892
2984
  const nextState = {
2893
2985
  version: CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION,
2894
2986
  models: { ...state.models },
2895
- ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {}
2987
+ ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {},
2988
+ ...state.configuredEfforts ? { configuredEfforts: { ...state.configuredEfforts } } : {}
2896
2989
  };
2897
2990
  try {
2898
2991
  for (const override of overrides) {
2899
- if (!isClaudeCodeModelAlias(override.model)) {
2992
+ if (!isClaudeCodeModelAlias(override.model) && override.catalogId !== override.model) {
2900
2993
  throw new Error(
2901
2994
  `Unsupported Claude Code model "${override.model}" for ${override.role}; use sonnet, opus, haiku, or inherit.`
2902
2995
  );
@@ -2908,12 +3001,25 @@ function applyClaudeCodeManagedModelOverrides(config, overrides) {
2908
3001
  throw new Error(`Missing Claude Code subagent for ${override.role}.`);
2909
3002
  }
2910
3003
  const before = existsSync6(roleItem.targetPath) ? readFileSync7(roleItem.targetPath, "utf8") : roleItem.content;
2911
- const updated = replaceSubagentModel(before, override.model);
3004
+ let updated = replaceSubagentModel(before, override.model);
3005
+ if (override.clearEffort) {
3006
+ updated = replaceSubagentEffort(updated, void 0);
3007
+ } else if (override.effort !== void 0) {
3008
+ updated = replaceSubagentEffort(updated, override.effort);
3009
+ }
2912
3010
  if (writeTextWithBackup(roleItem.targetPath, updated)) {
2913
3011
  changed.push(roleItem.targetPath);
2914
3012
  }
2915
3013
  nextState.configuredModels ??= {};
2916
3014
  nextState.configuredModels[override.role] = override.model;
3015
+ if (override.clearEffort) {
3016
+ if (nextState.configuredEfforts) {
3017
+ delete nextState.configuredEfforts[override.role];
3018
+ }
3019
+ } else if (override.effort !== void 0) {
3020
+ nextState.configuredEfforts ??= {};
3021
+ nextState.configuredEfforts[override.role] = override.effort;
3022
+ }
2917
3023
  }
2918
3024
  if (writeTextWithBackup(statePath, stableJson3(nextState))) {
2919
3025
  changed.push(statePath);
@@ -2939,6 +3045,31 @@ function formatClaudeCodeSetupPlan(plan) {
2939
3045
  var CLAUDE_CODE_DISPLAY_NAME = "Claude Code";
2940
3046
  var claudeCodePlanSources = /* @__PURE__ */ new WeakMap();
2941
3047
  var claudeCodeModelSources = /* @__PURE__ */ new WeakMap();
3048
+ var CLAUDE_CODE_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
3049
+ function resolveClaudeCodeEffort(input) {
3050
+ if (!input.effort || input.effort.kind === "inherit") {
3051
+ return { ok: true, effort: void 0 };
3052
+ }
3053
+ const effort = input.effort.value;
3054
+ if (!CLAUDE_CODE_EFFORTS.has(effort)) {
3055
+ return {
3056
+ ok: false,
3057
+ code: "claude-code-effort-runtime-unsupported",
3058
+ message: `Claude Code does not support effort ${effort}.`
3059
+ };
3060
+ }
3061
+ if (isClaudeCodeModelAlias(input.model) && input.model !== "inherit") {
3062
+ return { ok: true, effort };
3063
+ }
3064
+ if (input.catalogId === input.model && input.availableEfforts?.includes(effort)) {
3065
+ return { ok: true, effort };
3066
+ }
3067
+ return {
3068
+ ok: false,
3069
+ code: "claude-code-effort-catalog-unsupported",
3070
+ message: `Effort ${effort} is not supported by ${input.catalogId ?? input.model} in the models.dev catalog.`
3071
+ };
3072
+ }
2942
3073
  var claudeCodeActions = [
2943
3074
  {
2944
3075
  id: "claude-code-status",
@@ -3002,16 +3133,16 @@ var claudeCodeOperationAdapter = {
3002
3133
  description: "Claude Code plugin package and managed subagent surfaces.",
3003
3134
  actions: claudeCodeActions
3004
3135
  };
3005
- function claudeCodeConfig(context = { cwd: process.cwd() }, dryRun) {
3136
+ function claudeCodeConfig(context2 = { cwd: process.cwd() }, dryRun) {
3006
3137
  return {
3007
3138
  dryRun,
3008
3139
  reset: false,
3009
3140
  // Match the installer (install.ts uses 'user') so post-install status,
3010
3141
  // update, sync, and model commands resolve the same plugin root.
3011
- scope: context.scope ?? "user",
3012
- projectRoot: context.cwd,
3013
- homeDir: context.homeDir,
3014
- packageRoot: context.packageRoot
3142
+ scope: context2.scope ?? "user",
3143
+ projectRoot: context2.cwd,
3144
+ homeDir: context2.homeDir,
3145
+ packageRoot: context2.packageRoot
3015
3146
  };
3016
3147
  }
3017
3148
  function claudeCodeDisclaimers() {
@@ -3131,10 +3262,10 @@ function statusFromSetupPlan(plan) {
3131
3262
  ]
3132
3263
  };
3133
3264
  }
3134
- function getClaudeCodeStatus(context = { cwd: process.cwd() }) {
3265
+ function getClaudeCodeStatus(context2 = { cwd: process.cwd() }) {
3135
3266
  let plan;
3136
3267
  try {
3137
- plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context, true));
3268
+ plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true));
3138
3269
  } catch (error) {
3139
3270
  const message = error instanceof Error ? error.message : String(error);
3140
3271
  return {
@@ -3192,53 +3323,71 @@ function planFromSetup(id, action, title, summary, setupPlan) {
3192
3323
  claudeCodePlanSources.set(plan, setupPlan);
3193
3324
  return plan;
3194
3325
  }
3195
- function buildClaudeCodeInstallPlan(context = { cwd: process.cwd() }) {
3326
+ function buildClaudeCodeInstallPlan(context2 = { cwd: process.cwd() }) {
3196
3327
  return planFromSetup(
3197
3328
  "claude-code-install-preview",
3198
3329
  "install",
3199
3330
  "Install Claude Code plugin package",
3200
3331
  "Preview Claude Code plugin package install using buildClaudeCodeSetupPlan().",
3201
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3332
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3202
3333
  );
3203
3334
  }
3204
- function buildClaudeCodeUpdatePlan(context = { cwd: process.cwd() }) {
3335
+ function buildClaudeCodeUpdatePlan(context2 = { cwd: process.cwd() }) {
3205
3336
  return planFromSetup(
3206
3337
  "claude-code-update-preview",
3207
3338
  "update",
3208
3339
  "Update Claude Code plugin package",
3209
3340
  "Preview Claude Code managed plugin refresh using buildClaudeCodeSetupPlan().",
3210
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3341
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3211
3342
  );
3212
3343
  }
3213
- function buildClaudeCodeSyncPlan(context = { cwd: process.cwd() }) {
3344
+ function buildClaudeCodeSyncPlan(context2 = { cwd: process.cwd() }) {
3214
3345
  return planFromSetup(
3215
3346
  "claude-code-sync-preview",
3216
3347
  "sync",
3217
3348
  "Sync Claude Code plugin package",
3218
3349
  "Preview Claude Code managed plugin subagents, MCP, hooks, and skills.",
3219
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3350
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3220
3351
  );
3221
3352
  }
3222
3353
  function isClaudeCodeRole(role) {
3223
3354
  return CLAUDE_CODE_ROLE_NAMES.includes(role);
3224
3355
  }
3225
- function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3226
- const status = getClaudeCodeStatus(context);
3227
- const supportedRoles = input.roles.filter((role) => isClaudeCodeRole(role.role)).filter((role) => isClaudeCodeModelAlias(role.model)).map((role) => ({
3356
+ function buildClaudeCodeModelPlan(input, context2 = { cwd: process.cwd() }) {
3357
+ const status = getClaudeCodeStatus(context2);
3358
+ const resolvedRoles = input.roles.map((role) => ({
3359
+ role,
3360
+ effort: resolveClaudeCodeEffort(role),
3361
+ validRole: isClaudeCodeRole(role.role),
3362
+ validModel: isClaudeCodeModelAlias(role.model) || role.catalogId === role.model
3363
+ }));
3364
+ const supportedRoles = resolvedRoles.filter((entry) => entry.validRole && entry.validModel && entry.effort.ok).map(({ role, effort }) => ({
3228
3365
  role: role.role,
3229
- model: role.model
3366
+ model: role.model,
3367
+ ...role.catalogId ? { catalogId: role.catalogId } : {},
3368
+ ...effort.ok && effort.effort !== void 0 ? { effort: effort.effort } : {},
3369
+ ...role.effort?.kind === "inherit" ? { clearEffort: true } : {}
3230
3370
  }));
3231
- const rejectedRoles = input.roles.filter(
3232
- (role) => !isClaudeCodeRole(role.role) || !isClaudeCodeModelAlias(role.model)
3371
+ const rejectedRoles = resolvedRoles.filter(
3372
+ (entry) => !entry.validRole || !entry.validModel
3373
+ );
3374
+ const effortErrors = resolvedRoles.filter(
3375
+ (entry) => entry.validRole && entry.validModel && !entry.effort.ok
3233
3376
  );
3234
3377
  const warnings = [
3235
3378
  ...status.diagnostics,
3236
3379
  ...input.warnings ?? [],
3237
3380
  ...rejectedRoles.map(
3238
- (role) => warning(
3381
+ ({ role }) => warning(
3239
3382
  `Claude Code does not accept role "${role.role}" with model "${role.model}"; roles must be one of ${CLAUDE_CODE_ROLE_NAMES.join(", ")} and models must be sonnet, opus, haiku, or inherit.`,
3240
3383
  "claude-code-unsupported-model-role"
3241
3384
  )
3385
+ ),
3386
+ ...effortErrors.map(
3387
+ ({ effort }) => warning(
3388
+ effort.ok ? "Unknown Claude Code effort error." : effort.message,
3389
+ effort.ok ? "claude-code-effort-unknown" : effort.code
3390
+ )
3242
3391
  )
3243
3392
  ];
3244
3393
  if (input.harness !== "claude") {
@@ -3269,7 +3418,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3269
3418
  title: "Configure Claude Code subagent model lines",
3270
3419
  summary: "Preview model changes for generated Claude Code subagent files and managed model state only.",
3271
3420
  dryRun: true,
3272
- canApply: input.harness === "claude" && supportedRoles.length > 0 && status.state !== "unknown",
3421
+ canApply: input.harness === "claude" && supportedRoles.length > 0 && effortErrors.length === 0 && status.state !== "unknown",
3273
3422
  targets: [...targets, ...stateTarget ? [stateTarget] : []],
3274
3423
  surfaces: targets.map((target) => ({
3275
3424
  id: `claude-code-model:${target.label}`,
@@ -3282,7 +3431,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3282
3431
  strategy: "managed-backup-file",
3283
3432
  description: "Existing subagent files and managed model state are backed up by the managed write helper."
3284
3433
  },
3285
- items: supportedRoles.map(({ role, model }) => ({
3434
+ items: supportedRoles.map(({ role, model, effort }) => ({
3286
3435
  title: `Set ${role} Claude Code subagent model line`,
3287
3436
  target: targets.find(
3288
3437
  (target) => target.path?.endsWith(`agents${pathSep()}${role}.md`)
@@ -3290,7 +3439,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3290
3439
  kind: "generated-artifact",
3291
3440
  label: `Claude Code ${role} subagent`
3292
3441
  },
3293
- preview: JSON.stringify({ role, model }),
3442
+ preview: JSON.stringify({ role, model, effort: effort ?? null }),
3294
3443
  backup: { required: true, strategy: "managed-backup-file" }
3295
3444
  })),
3296
3445
  warnings,
@@ -3304,7 +3453,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3304
3453
  ]
3305
3454
  };
3306
3455
  claudeCodeModelSources.set(plan, {
3307
- config: claudeCodeConfig(context, false),
3456
+ config: claudeCodeConfig(context2, false),
3308
3457
  roles: supportedRoles
3309
3458
  });
3310
3459
  return plan;
@@ -3360,7 +3509,10 @@ function applyClaudeCodePlan(plan) {
3360
3509
  source.config,
3361
3510
  source.roles.map((role) => ({
3362
3511
  role: role.role,
3363
- model: role.model
3512
+ model: role.model,
3513
+ ...role.catalogId ? { catalogId: role.catalogId } : {},
3514
+ ...role.effort ? { effort: role.effort } : {},
3515
+ ...role.clearEffort ? { clearEffort: true } : {}
3364
3516
  }))
3365
3517
  );
3366
3518
  return {
@@ -3479,16 +3631,16 @@ var codexOperationAdapter = {
3479
3631
  description: "Codex agent-pack and managed subagent surfaces.",
3480
3632
  actions: codexActions
3481
3633
  };
3482
- function codexConfig(context = { cwd: process.cwd() }, dryRun) {
3634
+ function codexConfig(context2 = { cwd: process.cwd() }, dryRun) {
3483
3635
  return {
3484
3636
  dryRun,
3485
3637
  reset: false,
3486
- scope: context.scope ?? "user",
3487
- projectRoot: context.cwd,
3488
- homeDir: context.homeDir,
3489
- codexHome: context.codexHome,
3490
- packageRoot: context.packageRoot,
3491
- pluginId: context.pluginId
3638
+ scope: context2.scope ?? "user",
3639
+ projectRoot: context2.cwd,
3640
+ homeDir: context2.homeDir,
3641
+ codexHome: context2.codexHome,
3642
+ packageRoot: context2.packageRoot,
3643
+ pluginId: context2.pluginId
3492
3644
  };
3493
3645
  }
3494
3646
  function codexDisclaimers() {
@@ -3654,10 +3806,10 @@ function statusSummary2(state) {
3654
3806
  return "Codex managed setup could not be classified safely.";
3655
3807
  }
3656
3808
  }
3657
- function getCodexStatus(context = { cwd: process.cwd() }) {
3809
+ function getCodexStatus(context2 = { cwd: process.cwd() }) {
3658
3810
  let plan;
3659
3811
  try {
3660
- plan = buildCodexSetupPlan(codexConfig(context, true));
3812
+ plan = buildCodexSetupPlan(codexConfig(context2, true));
3661
3813
  } catch (error) {
3662
3814
  const message = error instanceof Error ? error.message : String(error);
3663
3815
  return {
@@ -3708,8 +3860,8 @@ function planItemFromSetup2(item) {
3708
3860
  backup: backupForItem2(item)
3709
3861
  };
3710
3862
  }
3711
- function planFromSetup2(id, action, title, summary, setupPlan, context) {
3712
- const status = getCodexStatus(context);
3863
+ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3864
+ const status = getCodexStatus(context2);
3713
3865
  const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated";
3714
3866
  const plan = {
3715
3867
  id,
@@ -3744,37 +3896,37 @@ function planFromSetup2(id, action, title, summary, setupPlan, context) {
3744
3896
  codexPlanSources.set(plan, setupPlan);
3745
3897
  return plan;
3746
3898
  }
3747
- function buildCodexUpdatePlan(context = { cwd: process.cwd() }) {
3748
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3899
+ function buildCodexUpdatePlan(context2 = { cwd: process.cwd() }) {
3900
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3749
3901
  return planFromSetup2(
3750
3902
  "codex-update-preview",
3751
3903
  "update",
3752
3904
  "Update Codex managed setup",
3753
3905
  "Preview Codex managed setup refresh using buildCodexSetupPlan().",
3754
3906
  setupPlan,
3755
- context
3907
+ context2
3756
3908
  );
3757
3909
  }
3758
- function buildCodexSyncPlan(context = { cwd: process.cwd() }) {
3759
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3910
+ function buildCodexSyncPlan(context2 = { cwd: process.cwd() }) {
3911
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3760
3912
  return planFromSetup2(
3761
3913
  "codex-sync-preview",
3762
3914
  "sync",
3763
3915
  "Sync Codex managed configuration",
3764
3916
  "Preview Codex managed root instructions, subagents, plugin source, marketplace entry, and feature gates.",
3765
3917
  setupPlan,
3766
- context
3918
+ context2
3767
3919
  );
3768
3920
  }
3769
- function buildCodexInstallPlan(context = { cwd: process.cwd() }) {
3770
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3921
+ function buildCodexInstallPlan(context2 = { cwd: process.cwd() }) {
3922
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3771
3923
  return planFromSetup2(
3772
3924
  "codex-install-preview",
3773
3925
  "install",
3774
3926
  "Install Codex managed setup",
3775
3927
  "Preview Codex managed agent-pack setup using buildCodexSetupPlan().",
3776
3928
  setupPlan,
3777
- context
3929
+ context2
3778
3930
  );
3779
3931
  }
3780
3932
  function normalizeCodexModel(input) {
@@ -3783,15 +3935,55 @@ function normalizeCodexModel(input) {
3783
3935
  }
3784
3936
  return input.model;
3785
3937
  }
3938
+ var CODEX_DOCUMENTED_EFFORTS = /* @__PURE__ */ new Set([
3939
+ "none",
3940
+ "minimal",
3941
+ "low",
3942
+ "medium",
3943
+ "high",
3944
+ "xhigh",
3945
+ "max",
3946
+ "ultra"
3947
+ ]);
3948
+ function resolveCodexEffort(input) {
3949
+ if (!input.effort || input.effort.kind === "inherit") {
3950
+ return { ok: true, value: void 0 };
3951
+ }
3952
+ const value = input.effort.value;
3953
+ if (!CODEX_DOCUMENTED_EFFORTS.has(value)) {
3954
+ return {
3955
+ ok: false,
3956
+ code: "codex-effort-undocumented",
3957
+ message: `Codex does not document reasoning effort "${value}".`
3958
+ };
3959
+ }
3960
+ if (!input.catalogId?.startsWith("openai/") || !input.availableEfforts?.includes(value)) {
3961
+ return {
3962
+ ok: false,
3963
+ code: "codex-effort-model-unsupported",
3964
+ message: `Model ${input.catalogId ?? input.model} does not publish Codex effort "${value}".`
3965
+ };
3966
+ }
3967
+ return { ok: true, value };
3968
+ }
3786
3969
  function isCodexRole(role) {
3787
3970
  return CODEX_ROLE_NAMES.includes(role);
3788
3971
  }
3789
- function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3790
- const status = getCodexStatus(context);
3791
- const supportedRoles = input.roles.filter((role) => isCodexRole(role.role)).map((role) => ({
3972
+ function buildCodexModelPlan(input, context2 = { cwd: process.cwd() }) {
3973
+ const status = getCodexStatus(context2);
3974
+ const candidateRoles = input.roles.filter((role) => isCodexRole(role.role)).map((role) => ({
3792
3975
  role: role.role,
3793
- model: normalizeCodexModel(role)
3976
+ model: normalizeCodexModel(role),
3977
+ effort: resolveCodexEffort(role),
3978
+ clearEffort: role.effort?.kind === "inherit"
3794
3979
  }));
3980
+ const supportedRoles = candidateRoles.filter((role) => role.effort.ok).map((role) => ({
3981
+ role: role.role,
3982
+ model: role.model,
3983
+ ...role.effort.value ? { effort: role.effort.value } : {},
3984
+ ...role.clearEffort ? { clearEffort: true } : {}
3985
+ }));
3986
+ const effortErrors = candidateRoles.filter((role) => !role.effort.ok);
3795
3987
  const unsupportedRoles = input.roles.filter(
3796
3988
  (role) => !isCodexRole(role.role)
3797
3989
  );
@@ -3803,6 +3995,12 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3803
3995
  `Codex does not expose a supported generated model surface for ${role.role}; this plan will not write that role.`,
3804
3996
  "codex-unsupported-model-role"
3805
3997
  )
3998
+ ),
3999
+ ...effortErrors.map(
4000
+ (role) => warning2(
4001
+ role.effort.message,
4002
+ role.effort.code
4003
+ )
3806
4004
  )
3807
4005
  ];
3808
4006
  if (input.harness !== "codex") {
@@ -3823,7 +4021,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3823
4021
  state: "missing"
3824
4022
  };
3825
4023
  });
3826
- const config = codexConfig(context, false);
4024
+ const config = codexConfig(context2, false);
3827
4025
  const stateTarget = status.targets.find(
3828
4026
  (target) => target.path?.endsWith(".thoth-agents-managed-models.json")
3829
4027
  );
@@ -3834,7 +4032,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3834
4032
  title: "Configure Codex subagent model lines",
3835
4033
  summary: "Preview model changes for generated Codex subagent TOML files and .thoth-agents-managed-models.json only.",
3836
4034
  dryRun: true,
3837
- canApply: input.harness === "codex" && supportedRoles.length > 0 && status.state !== "unknown",
4035
+ canApply: input.harness === "codex" && supportedRoles.length > 0 && effortErrors.length === 0 && status.state !== "unknown",
3838
4036
  targets: [...targets, ...stateTarget ? [stateTarget] : []],
3839
4037
  surfaces: targets.map((target) => ({
3840
4038
  id: `codex-model:${target.label}`,
@@ -3847,7 +4045,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3847
4045
  strategy: "managed-backup-file",
3848
4046
  description: "Existing subagent TOML and managed model state files are backed up by the managed write helper."
3849
4047
  },
3850
- items: supportedRoles.map(({ role, model }) => ({
4048
+ items: supportedRoles.map(({ role, model, effort }) => ({
3851
4049
  title: `Set ${role} Codex subagent model line`,
3852
4050
  target: targets.find(
3853
4051
  (target) => target.path?.endsWith(`thoth-agents-${role}.toml`)
@@ -3855,7 +4053,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3855
4053
  kind: "generated-artifact",
3856
4054
  label: `Codex ${role} subagent`
3857
4055
  },
3858
- preview: JSON.stringify({ role, model }),
4056
+ preview: JSON.stringify({ role, model, effort }),
3859
4057
  backup: {
3860
4058
  required: true,
3861
4059
  strategy: "managed-backup-file"
@@ -3971,6 +4169,36 @@ function applyCodexPlan(plan) {
3971
4169
  import { existsSync as existsSync10 } from "fs";
3972
4170
  import { join as join10 } from "path";
3973
4171
 
4172
+ // src/cli/opencode-effort.ts
4173
+ var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
4174
+ "none",
4175
+ "low",
4176
+ "medium",
4177
+ "high",
4178
+ "xhigh"
4179
+ ]);
4180
+ function resolveOpenCodeEffort(input) {
4181
+ if (!input.effort || input.effort.kind === "inherit") {
4182
+ return { ok: true, variant: void 0 };
4183
+ }
4184
+ const variant = input.effort.value;
4185
+ if (!input.availableEfforts?.includes(variant)) {
4186
+ return {
4187
+ ok: false,
4188
+ code: "opencode-effort-catalog-unsupported",
4189
+ message: `Effort ${variant} is not supported by ${input.catalogId ?? input.model} in the models.dev catalog.`
4190
+ };
4191
+ }
4192
+ if (input.catalogId?.startsWith("openai/gpt-") && OPENAI_GPT_RUNTIME_VARIANTS.has(variant)) {
4193
+ return { ok: true, variant };
4194
+ }
4195
+ return {
4196
+ ok: false,
4197
+ code: "opencode-effort-runtime-unconfirmed",
4198
+ message: `Effort ${variant} is catalogued for ${input.catalogId ?? input.model}, but the OpenCode runtime mapping is not confirmed.`
4199
+ };
4200
+ }
4201
+
3974
4202
  // src/cli/skills.ts
3975
4203
  import { spawnSync } from "child_process";
3976
4204
  import { existsSync as existsSync9 } from "fs";
@@ -4141,11 +4369,11 @@ function titleCaseSkillName(value) {
4141
4369
  return `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`;
4142
4370
  }).join("-");
4143
4371
  }
4144
- function homeDirFromContext(context) {
4145
- return context.env?.HOME ?? context.env?.USERPROFILE;
4372
+ function homeDirFromContext(context2) {
4373
+ return context2.env?.HOME ?? context2.env?.USERPROFILE;
4146
4374
  }
4147
- function openCodeSkillTargets(context) {
4148
- const homeDir = homeDirFromContext(context);
4375
+ function openCodeSkillTargets(context2) {
4376
+ const homeDir = homeDirFromContext(context2);
4149
4377
  const recommendedTargets = RECOMMENDED_SKILLS.map(
4150
4378
  (skill) => {
4151
4379
  const path4 = getRecommendedSkillPath(skill, homeDir);
@@ -4226,13 +4454,13 @@ function hasSevenAgentPreset(config) {
4226
4454
  function classifyApplySafety(state) {
4227
4455
  return state === "missing" || state === "installed";
4228
4456
  }
4229
- function getOpenCodeStatus(context = { cwd: process.cwd() }) {
4457
+ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
4230
4458
  const mainPath = getExistingConfigPath();
4231
4459
  const litePath = getExistingLiteConfigPath();
4232
4460
  const main = parseConfig(mainPath);
4233
4461
  const lite = parseConfig(litePath);
4234
4462
  const diagnostics = [];
4235
- const skillStatus = openCodeSkillTargets(context);
4463
+ const skillStatus = openCodeSkillTargets(context2);
4236
4464
  if (main.error) {
4237
4465
  diagnostics.push({
4238
4466
  severity: "critical",
@@ -4554,11 +4782,26 @@ function normalizeRoleModel(input) {
4554
4782
  function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4555
4783
  const items = input.roles.map((role) => {
4556
4784
  const model = normalizeRoleModel(role);
4785
+ const effort = resolveOpenCodeEffort(role);
4557
4786
  return {
4558
4787
  title: `Set ${role.role} OpenCode model override`,
4559
4788
  target: targetForLiteConfig(),
4560
- preview: JSON.stringify({ [role.role]: { model } }),
4561
- backup: defaultBackup(getExistingLiteConfigPath())
4789
+ preview: JSON.stringify({
4790
+ [role.role]: {
4791
+ model,
4792
+ variant: effort.ok ? effort.variant ?? null : null
4793
+ }
4794
+ }),
4795
+ backup: defaultBackup(getExistingLiteConfigPath()),
4796
+ ...!effort.ok ? {
4797
+ warnings: [
4798
+ {
4799
+ severity: "critical",
4800
+ code: effort.code,
4801
+ message: effort.message
4802
+ }
4803
+ ]
4804
+ } : {}
4562
4805
  };
4563
4806
  });
4564
4807
  const plan = planFromItems(
@@ -4568,6 +4811,11 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4568
4811
  "Preview thoth-agents role model overrides in the plugin config agents map.",
4569
4812
  items
4570
4813
  );
4814
+ const effortWarnings = items.flatMap((item) => item.warnings ?? []);
4815
+ if (effortWarnings.length > 0) {
4816
+ plan.canApply = false;
4817
+ plan.warnings.push(...effortWarnings);
4818
+ }
4571
4819
  if (input.harness !== "opencode") {
4572
4820
  return {
4573
4821
  ...plan,
@@ -4693,13 +4941,14 @@ function applyModelPlan(plan) {
4693
4941
  }
4694
4942
  const role = match[1] ?? "";
4695
4943
  const model = parsed2[role]?.model;
4696
- if (!ROLE_NAMES.includes(role) || typeof model !== "string") {
4944
+ const variant = parsed2[role]?.variant;
4945
+ if (!ROLE_NAMES.includes(role) || typeof model !== "string" || variant !== null && typeof variant !== "string") {
4697
4946
  return rejectPlan3(
4698
4947
  plan,
4699
4948
  "OpenCode model plan contains an invalid role or model."
4700
4949
  );
4701
4950
  }
4702
- roleModels.set(role, model);
4951
+ roleModels.set(role, { model, variant });
4703
4952
  }
4704
4953
  if (roleModels.size === 0) {
4705
4954
  return rejectPlan3(
@@ -4719,14 +4968,44 @@ function applyModelPlan(plan) {
4719
4968
  reset: false
4720
4969
  });
4721
4970
  const agents = base.agents && typeof base.agents === "object" ? { ...base.agents } : {};
4722
- for (const role of roleModels.keys()) {
4723
- agents[role] = {
4724
- ...agents[role] ?? {},
4725
- model: roleModels.get(role)
4971
+ const statePath = getOpenCodeManagedModelStatePath();
4972
+ const state = readManagedModelState(statePath, 1);
4973
+ const configuredEfforts = { ...state.configuredEfforts ?? {} };
4974
+ const warnings = [];
4975
+ for (const [role, override] of roleModels) {
4976
+ const current = {
4977
+ ...agents[role] ?? {}
4726
4978
  };
4979
+ const currentVariant = typeof current.variant === "string" ? current.variant : void 0;
4980
+ const trackedVariant = configuredEfforts[role];
4981
+ const userOwnsVariant = override.variant === null && currentVariant !== void 0 && (trackedVariant === void 0 || currentVariant !== trackedVariant);
4982
+ current.model = override.model;
4983
+ if (userOwnsVariant) {
4984
+ delete configuredEfforts[role];
4985
+ warnings.push({
4986
+ severity: "important",
4987
+ code: "opencode-effort-user-owned",
4988
+ message: `Preserved user-owned OpenCode variant ${currentVariant} for ${role}.`
4989
+ });
4990
+ } else if (override.variant === null) {
4991
+ delete current.variant;
4992
+ delete configuredEfforts[role];
4993
+ } else {
4994
+ current.variant = override.variant;
4995
+ configuredEfforts[role] = override.variant;
4996
+ }
4997
+ agents[role] = current;
4727
4998
  }
4728
4999
  base.agents = agents;
4729
5000
  writeConfig(targetPath, base);
5001
+ writeTextWithBackup(
5002
+ statePath,
5003
+ stableJson3({
5004
+ version: 1,
5005
+ models: state.models,
5006
+ ...Object.keys(configuredEfforts).length > 0 ? { configuredEfforts } : {}
5007
+ })
5008
+ );
4730
5009
  return {
4731
5010
  harness: "opencode",
4732
5011
  action: "model-config",
@@ -4739,7 +5018,7 @@ function applyModelPlan(plan) {
4739
5018
  }
4740
5019
  ],
4741
5020
  backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
4742
- warnings: [],
5021
+ warnings,
4743
5022
  disclaimers: defaultDisclaimers()
4744
5023
  };
4745
5024
  }
@@ -4868,23 +5147,571 @@ function getOperationHarness(harness) {
4868
5147
  return OPERATION_HARNESSES[harness];
4869
5148
  }
4870
5149
 
5150
+ // src/cli/tui/model-catalog.ts
5151
+ import { execFileSync } from "child_process";
5152
+
5153
+ // src/cli/model-catalog/cache.ts
5154
+ import {
5155
+ existsSync as existsSync11,
5156
+ mkdirSync as mkdirSync3,
5157
+ readFileSync as readFileSync10,
5158
+ renameSync as renameSync2,
5159
+ unlinkSync,
5160
+ writeFileSync as writeFileSync3
5161
+ } from "fs";
5162
+ import { homedir as homedir4 } from "os";
5163
+ import { dirname as dirname5, posix, win32 } from "path";
5164
+ import { z as z2 } from "zod";
5165
+ var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
5166
+ var cachedModelOptionSchema = z2.object({
5167
+ id: z2.string().min(1),
5168
+ catalogId: z2.string().min(1).optional(),
5169
+ label: z2.string().min(1),
5170
+ provider: z2.string().min(1),
5171
+ efforts: z2.array(z2.string().min(1)),
5172
+ source: modelCatalogSourceSchema
5173
+ }).passthrough();
5174
+ var modelsDevCacheSchema = z2.object({
5175
+ version: z2.literal(1),
5176
+ etag: z2.string().optional(),
5177
+ fetchedAt: z2.string().datetime(),
5178
+ catalog: z2.object({ models: z2.array(cachedModelOptionSchema) }).passthrough()
5179
+ }).passthrough();
5180
+ function resolveModelsDevCachePath(options = {}) {
5181
+ const platform = options.platform ?? process.platform;
5182
+ const env = options.env ?? process.env;
5183
+ const home = options.homeDir ?? homedir4();
5184
+ if (platform === "win32") {
5185
+ const path4 = win32;
5186
+ const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path4.join(home, "AppData", "Local");
5187
+ return path4.join(base2, "thoth-agents", "models-dev-v1.json");
5188
+ }
5189
+ const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
5190
+ return posix.join(base, "thoth-agents", "models-dev-v1.json");
5191
+ }
5192
+ function readModelsDevCache(cachePath) {
5193
+ if (!existsSync11(cachePath)) return void 0;
5194
+ try {
5195
+ const input = JSON.parse(readFileSync10(cachePath, "utf8"));
5196
+ const parsed = modelsDevCacheSchema.safeParse(input);
5197
+ return parsed.success ? parsed.data : void 0;
5198
+ } catch {
5199
+ return void 0;
5200
+ }
5201
+ }
5202
+ function writeModelsDevCacheAtomic(cachePath, cache) {
5203
+ const validated = modelsDevCacheSchema.safeParse(cache);
5204
+ if (!validated.success) {
5205
+ throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
5206
+ }
5207
+ mkdirSync3(dirname5(cachePath), { recursive: true });
5208
+ const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
5209
+ try {
5210
+ writeFileSync3(
5211
+ temporaryPath,
5212
+ `${JSON.stringify(validated.data, null, 2)}
5213
+ `
5214
+ );
5215
+ renameSync2(temporaryPath, cachePath);
5216
+ } catch (error) {
5217
+ if (existsSync11(temporaryPath)) unlinkSync(temporaryPath);
5218
+ throw error;
5219
+ }
5220
+ }
5221
+
5222
+ // src/cli/model-catalog/models-dev.ts
5223
+ import { z as z3 } from "zod";
5224
+ var modelsDevModelSchema = z3.object({
5225
+ id: z3.string().min(1).optional(),
5226
+ name: z3.string().min(1).optional(),
5227
+ reasoning: z3.boolean().optional(),
5228
+ reasoning_options: z3.array(z3.unknown()).optional()
5229
+ }).passthrough();
5230
+ var modelsDevProviderSchema = z3.object({
5231
+ models: z3.record(z3.string(), modelsDevModelSchema)
5232
+ }).passthrough();
5233
+ var modelsDevCatalogSchema = z3.record(
5234
+ z3.string(),
5235
+ modelsDevProviderSchema
5236
+ );
5237
+ var effortOptionSchema = z3.object({
5238
+ type: z3.literal("effort"),
5239
+ values: z3.array(z3.string().min(1))
5240
+ }).passthrough();
5241
+ function normalizedCatalogId(provider, modelKey, declaredId) {
5242
+ const candidate = (declaredId ?? modelKey).trim().toLowerCase();
5243
+ return candidate.includes("/") ? candidate : `${provider}/${candidate}`;
5244
+ }
5245
+ function runtimeModelId(provider, catalogId) {
5246
+ const prefix = `${provider}/`;
5247
+ return catalogId.startsWith(prefix) ? catalogId.slice(prefix.length) : catalogId;
5248
+ }
5249
+ function effortValues(options) {
5250
+ const values = /* @__PURE__ */ new Set();
5251
+ for (const option of options ?? []) {
5252
+ const parsed = effortOptionSchema.safeParse(option);
5253
+ if (!parsed.success) continue;
5254
+ for (const value of parsed.data.values) {
5255
+ const normalized = value.trim();
5256
+ if (normalized) values.add(normalized);
5257
+ }
5258
+ }
5259
+ return [...values];
5260
+ }
5261
+ function normalizeModelsDevCatalog(input) {
5262
+ const parsed = modelsDevCatalogSchema.safeParse(input);
5263
+ if (!parsed.success) {
5264
+ return {
5265
+ ok: false,
5266
+ issues: parsed.error.issues.map(
5267
+ (issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`
5268
+ )
5269
+ };
5270
+ }
5271
+ const models = [];
5272
+ for (const [rawProvider, provider] of Object.entries(parsed.data)) {
5273
+ const providerId = rawProvider.trim().toLowerCase();
5274
+ for (const [modelKey, model] of Object.entries(provider.models)) {
5275
+ const catalogId = normalizedCatalogId(providerId, modelKey, model.id);
5276
+ const id = runtimeModelId(providerId, catalogId);
5277
+ models.push({
5278
+ id,
5279
+ catalogId,
5280
+ label: model.name ?? id,
5281
+ provider: providerId,
5282
+ efforts: effortValues(model.reasoning_options),
5283
+ source: "remote"
5284
+ });
5285
+ }
5286
+ }
5287
+ return { ok: true, catalog: { models } };
5288
+ }
5289
+
5290
+ // src/cli/model-catalog/index.ts
5291
+ var MODELS_DEV_URL = "https://models.dev/api.json";
5292
+ var MODELS_DEV_MAX_BYTES = 8 * 1024 * 1024;
5293
+ var MODELS_DEV_TIMEOUT_MS = 5e3;
5294
+ function ownsCatalogKey(model, key) {
5295
+ const separator = key.indexOf("/");
5296
+ return separator > 0 && model.provider === key.slice(0, separator);
5297
+ }
5298
+ function mergeModelCatalog(dynamic, manual) {
5299
+ const merged = /* @__PURE__ */ new Map();
5300
+ for (const model of dynamic) {
5301
+ const key = model.catalogId ?? model.id;
5302
+ const existing = merged.get(key);
5303
+ if (existing && ownsCatalogKey(existing, key) && !ownsCatalogKey(model, key)) {
5304
+ continue;
5305
+ }
5306
+ merged.set(key, model);
5307
+ }
5308
+ for (const model of manual) {
5309
+ const key = model.catalogId ?? model.id;
5310
+ if (!merged.has(key)) merged.set(key, model);
5311
+ }
5312
+ return [...merged.values()];
5313
+ }
5314
+ function lkgModels(models) {
5315
+ return models.map((model) => ({
5316
+ ...model,
5317
+ efforts: [...model.efforts],
5318
+ source: "lkg"
5319
+ }));
5320
+ }
5321
+ async function loadModelsDevCatalog(options = {}) {
5322
+ const cachePath = options.cachePath ?? resolveModelsDevCachePath();
5323
+ const manual = options.manual ?? [];
5324
+ const fetcher = options.fetcher ?? fetch;
5325
+ const checkedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
5326
+ const cached = readModelsDevCache(cachePath);
5327
+ const warnings = [];
5328
+ try {
5329
+ const headers = new Headers({ Accept: "application/json" });
5330
+ if (cached?.etag) headers.set("If-None-Match", cached.etag);
5331
+ const response = await fetcher(MODELS_DEV_URL, {
5332
+ headers,
5333
+ signal: AbortSignal.timeout(MODELS_DEV_TIMEOUT_MS)
5334
+ });
5335
+ if (response.status === 304) {
5336
+ if (!cached)
5337
+ throw new Error("models.dev returned 304 without an LKG cache");
5338
+ return {
5339
+ models: mergeModelCatalog(lkgModels(cached.catalog.models), manual),
5340
+ source: "lkg",
5341
+ stale: false,
5342
+ checkedAt,
5343
+ warnings
5344
+ };
5345
+ }
5346
+ if (!response.ok)
5347
+ throw new Error(`models.dev returned HTTP ${response.status}`);
5348
+ const text = await response.text();
5349
+ if (Buffer.byteLength(text, "utf8") > MODELS_DEV_MAX_BYTES) {
5350
+ throw new Error("models.dev response exceeded the maximum size");
5351
+ }
5352
+ const input = JSON.parse(text);
5353
+ const normalized = normalizeModelsDevCatalog(input);
5354
+ if (!normalized.ok) {
5355
+ throw new Error(
5356
+ `models.dev validation failed: ${normalized.issues.join("; ")}`
5357
+ );
5358
+ }
5359
+ const remoteModels = normalized.catalog.models.map((model) => ({
5360
+ ...model,
5361
+ source: "remote"
5362
+ }));
5363
+ writeModelsDevCacheAtomic(cachePath, {
5364
+ version: 1,
5365
+ ...response.headers.get("etag") ? { etag: response.headers.get("etag") ?? void 0 } : {},
5366
+ fetchedAt: checkedAt,
5367
+ catalog: { models: lkgModels(remoteModels) }
5368
+ });
5369
+ return {
5370
+ models: mergeModelCatalog(remoteModels, manual),
5371
+ source: "remote",
5372
+ stale: false,
5373
+ checkedAt,
5374
+ warnings
5375
+ };
5376
+ } catch (error) {
5377
+ warnings.push(error instanceof Error ? error.message : String(error));
5378
+ }
5379
+ if (cached) {
5380
+ return {
5381
+ models: mergeModelCatalog(lkgModels(cached.catalog.models), manual),
5382
+ source: "lkg",
5383
+ stale: true,
5384
+ checkedAt,
5385
+ warnings
5386
+ };
5387
+ }
5388
+ return {
5389
+ models: [...manual],
5390
+ source: "manual",
5391
+ stale: true,
5392
+ checkedAt,
5393
+ warnings
5394
+ };
5395
+ }
5396
+
5397
+ // src/cli/tui/model-catalog.ts
5398
+ var MODEL_CATALOG_TIMEOUT_MS = 5e3;
5399
+ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
5400
+ "none",
5401
+ "minimal",
5402
+ "low",
5403
+ "medium",
5404
+ "high",
5405
+ "xhigh",
5406
+ "max",
5407
+ "ultra"
5408
+ ]);
5409
+ var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
5410
+ function parseOpenCodeModels(output) {
5411
+ const seen = /* @__PURE__ */ new Set();
5412
+ const options = [];
5413
+ for (const line of output.split(/\r?\n/)) {
5414
+ const match = line.trim().match(/^([a-z0-9_-]+)\/([^\s{]+)/i);
5415
+ if (!match) continue;
5416
+ const id = `${match[1]}/${match[2]}`;
5417
+ if (seen.has(id)) continue;
5418
+ seen.add(id);
5419
+ options.push({
5420
+ id,
5421
+ catalogId: id,
5422
+ label: id,
5423
+ provider: match[1] ?? "unknown",
5424
+ efforts: [],
5425
+ source: "manual"
5426
+ });
5427
+ }
5428
+ return options;
5429
+ }
5430
+ function isCodexOpenAiModelId(id) {
5431
+ const match = id.match(/^gpt-(\d+)(?:[.-]|$)/);
5432
+ return match?.[1] !== void 0 && Number(match[1]) >= 5;
5433
+ }
5434
+ function writableEfforts(harness, option) {
5435
+ if (harness === "codex") {
5436
+ return option.efforts.filter(
5437
+ (effort) => CODEX_DOCUMENTED_EFFORTS2.has(effort)
5438
+ );
5439
+ }
5440
+ if (harness === "claude") {
5441
+ return option.efforts.filter(
5442
+ (effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
5443
+ );
5444
+ }
5445
+ return option.efforts.filter(
5446
+ (effort) => resolveOpenCodeEffort({
5447
+ model: option.id,
5448
+ catalogId: option.catalogId,
5449
+ availableEfforts: option.efforts,
5450
+ effort: { kind: "effort", value: effort }
5451
+ }).ok
5452
+ );
5453
+ }
5454
+ function effortChoicesForModel(option) {
5455
+ return ["inherit", ...option?.efforts ?? []];
5456
+ }
5457
+ function getOpenCodeModelsInvocation(platform = process.platform) {
5458
+ const options = {
5459
+ encoding: "utf8",
5460
+ stdio: ["ignore", "pipe", "ignore"],
5461
+ timeout: MODEL_CATALOG_TIMEOUT_MS
5462
+ };
5463
+ if (platform === "win32") {
5464
+ return {
5465
+ command: "opencode models",
5466
+ args: [],
5467
+ options: { ...options, shell: true }
5468
+ };
5469
+ }
5470
+ return { command: "opencode", args: ["models"], options };
5471
+ }
5472
+ function getNativeOpenCodeOptions() {
5473
+ try {
5474
+ const invocation = getOpenCodeModelsInvocation();
5475
+ return parseOpenCodeModels(
5476
+ execFileSync(invocation.command, invocation.args, invocation.options)
5477
+ );
5478
+ } catch {
5479
+ return [];
5480
+ }
5481
+ }
5482
+ var CLAUDE_CODE_MODEL_OPTIONS = [
5483
+ { id: "sonnet", label: "sonnet", provider: "anthropic" },
5484
+ { id: "opus", label: "opus", provider: "anthropic" },
5485
+ { id: "haiku", label: "haiku", provider: "anthropic" },
5486
+ {
5487
+ id: "inherit",
5488
+ label: "inherit (main session model)",
5489
+ provider: "anthropic"
5490
+ }
5491
+ ].map((option) => ({
5492
+ ...option,
5493
+ efforts: option.id === "inherit" ? [] : [...CLAUDE_CODE_EFFORTS2],
5494
+ source: "manual"
5495
+ }));
5496
+ async function getModelOptions(harness) {
5497
+ const native = harness === "opencode" ? getNativeOpenCodeOptions() : [];
5498
+ const loaded = await loadModelsDevCatalog({ manual: native });
5499
+ if (harness === "claude") {
5500
+ const aliases = CLAUDE_CODE_MODEL_OPTIONS.map((option) => ({
5501
+ ...option,
5502
+ efforts: [...option.efforts]
5503
+ }));
5504
+ const concrete = loaded.models.filter((option) => option.provider === "anthropic").map((option) => ({
5505
+ ...option,
5506
+ id: option.catalogId ?? option.id,
5507
+ efforts: writableEfforts(harness, option)
5508
+ }));
5509
+ return [...aliases, ...concrete];
5510
+ }
5511
+ if (harness === "codex") {
5512
+ return loaded.models.filter(
5513
+ (option) => option.provider === "openai" && isCodexOpenAiModelId(option.id)
5514
+ ).map((option) => ({
5515
+ ...option,
5516
+ efforts: writableEfforts(harness, option)
5517
+ }));
5518
+ }
5519
+ const catalogById = new Map(
5520
+ loaded.models.map((option) => [option.catalogId ?? option.id, option])
5521
+ );
5522
+ return native.map((option) => {
5523
+ const catalog = catalogById.get(option.catalogId ?? option.id);
5524
+ const enriched = catalog ? { ...catalog, id: option.id, catalogId: option.catalogId } : option;
5525
+ return { ...enriched, efforts: writableEfforts(harness, enriched) };
5526
+ });
5527
+ }
5528
+
5529
+ // src/cli/tui/operations.ts
5530
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
5531
+ var context = { cwd: process.cwd() };
5532
+ var codexContext = { cwd: process.cwd() };
5533
+ var claudeCodeContext = {
5534
+ cwd: process.cwd(),
5535
+ scope: "user"
5536
+ };
5537
+ var opencodeModelRoles = ALL_AGENT_NAMES.map(
5538
+ (role) => ({
5539
+ role,
5540
+ model: DEFAULT_MODELS[role] ?? "openai/gpt-5.4"
5541
+ })
5542
+ );
5543
+ var codexModelRoles = CODEX_ROLE_NAMES.map(
5544
+ (role) => ({
5545
+ role,
5546
+ model: "gpt-5.4-mini"
5547
+ })
5548
+ );
5549
+ var codexDefaultModels = new Map(
5550
+ codexModelRoles.map((role) => [role.role, role.model])
5551
+ );
5552
+ function codexInstallConfig(source, dryRun) {
5553
+ return {
5554
+ dryRun,
5555
+ reset: false,
5556
+ scope: source.scope ?? "user",
5557
+ projectRoot: source.cwd,
5558
+ homeDir: source.homeDir,
5559
+ codexHome: source.codexHome,
5560
+ packageRoot: source.packageRoot,
5561
+ pluginId: source.pluginId
5562
+ };
5563
+ }
5564
+ function getCodexModelRoles(source = codexContext) {
5565
+ try {
5566
+ const plan = buildCodexSetupPlan(codexInstallConfig(source, true));
5567
+ return CODEX_ROLE_NAMES.map((role) => {
5568
+ const item = plan.items.find(
5569
+ (candidate) => candidate.action === "write-role-toml" && candidate.role === role
5570
+ );
5571
+ const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
5572
+ const effort = content ? parseRoleTomlEffort(content) : void 0;
5573
+ return {
5574
+ role,
5575
+ model: (content ? parseRoleTomlModel(content) : void 0) ?? codexDefaultModels.get(role) ?? "gpt-5.4-mini",
5576
+ effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
5577
+ };
5578
+ });
5579
+ } catch {
5580
+ return codexModelRoles.map((role) => ({ ...role }));
5581
+ }
5582
+ }
5583
+ function getClaudeCodeModelRoles(source = claudeCodeContext) {
5584
+ try {
5585
+ const plan = buildClaudeCodeSetupPlan({
5586
+ dryRun: true,
5587
+ reset: false,
5588
+ scope: source.scope ?? "user",
5589
+ projectRoot: source.cwd,
5590
+ homeDir: source.homeDir,
5591
+ packageRoot: source.packageRoot
5592
+ });
5593
+ return CLAUDE_CODE_ROLE_NAMES.map((role) => {
5594
+ const item = plan.items.find(
5595
+ (candidate) => candidate.kind === "subagent" && candidate.role === role
5596
+ );
5597
+ const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
5598
+ const model = content ? parseSubagentModel(content) : void 0;
5599
+ const effort = content ? parseSubagentEffort(content) : void 0;
5600
+ return {
5601
+ role,
5602
+ model: model ?? "inherit",
5603
+ effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
5604
+ };
5605
+ });
5606
+ } catch {
5607
+ return defaultClaudeCodeModelRoles();
5608
+ }
5609
+ }
5610
+ function readRoleField(config, role, field) {
5611
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
5612
+ return void 0;
5613
+ }
5614
+ const record = config;
5615
+ const value = record[role];
5616
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5617
+ return void 0;
5618
+ }
5619
+ const fieldValue = value[field];
5620
+ return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : void 0;
5621
+ }
5622
+ function getOpenCodeModelRoles() {
5623
+ const parsed = parseConfig(getExistingLiteConfigPath());
5624
+ const agents = parsed.config?.agents && typeof parsed.config.agents === "object" ? parsed.config.agents : void 0;
5625
+ const presets = parsed.config?.presets && typeof parsed.config.presets === "object" ? parsed.config.presets : {};
5626
+ const openaiPreset = presets.openai && typeof presets.openai === "object" ? presets.openai : void 0;
5627
+ return ALL_AGENT_NAMES.map((role) => {
5628
+ const variant = readRoleField(agents, role, "variant") ?? readRoleField(openaiPreset, role, "variant");
5629
+ return {
5630
+ role,
5631
+ model: readRoleField(agents, role, "model") ?? readRoleField(openaiPreset, role, "model") ?? DEFAULT_MODELS[role] ?? "openai/gpt-5.4",
5632
+ effort: variant ? { kind: "effort", value: variant } : { kind: "inherit" }
5633
+ };
5634
+ });
5635
+ }
5636
+ function buildTuiModelPlan(harness, roles) {
5637
+ if (harness === "opencode") {
5638
+ return buildOpenCodeModelPlan({ harness, dryRun: true, roles }, context);
5639
+ }
5640
+ if (harness === "claude") {
5641
+ return buildClaudeCodeModelPlan(
5642
+ { harness, dryRun: true, roles },
5643
+ claudeCodeContext
5644
+ );
5645
+ }
5646
+ return buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
5647
+ }
5648
+ var defaultTuiOperations = {
5649
+ status(harness) {
5650
+ if (harness === "opencode") return getOpenCodeStatus(context);
5651
+ if (harness === "claude") {
5652
+ return getClaudeCodeStatus(claudeCodeContext);
5653
+ }
5654
+ return getCodexStatus(codexContext);
5655
+ },
5656
+ modelRoles(harness) {
5657
+ if (harness === "opencode") return getOpenCodeModelRoles();
5658
+ if (harness === "claude") {
5659
+ return getClaudeCodeModelRoles(claudeCodeContext);
5660
+ }
5661
+ return getCodexModelRoles(codexContext);
5662
+ },
5663
+ modelOptions(harness) {
5664
+ return getModelOptions(harness);
5665
+ },
5666
+ plan(harness, action) {
5667
+ if (harness === "opencode") {
5668
+ if (action === "install") return buildOpenCodeInstallPlan(context);
5669
+ if (action === "update") return buildOpenCodeUpdatePlan(context);
5670
+ if (action === "sync") return buildOpenCodeSyncPlan(context);
5671
+ return buildTuiModelPlan(harness, getOpenCodeModelRoles());
5672
+ }
5673
+ if (harness === "claude") {
5674
+ if (action === "install") {
5675
+ return buildClaudeCodeInstallPlan(claudeCodeContext);
5676
+ }
5677
+ if (action === "update") {
5678
+ return buildClaudeCodeUpdatePlan(claudeCodeContext);
5679
+ }
5680
+ if (action === "sync") return buildClaudeCodeSyncPlan(claudeCodeContext);
5681
+ return buildTuiModelPlan(
5682
+ harness,
5683
+ getClaudeCodeModelRoles(claudeCodeContext)
5684
+ );
5685
+ }
5686
+ if (action === "install") return buildCodexInstallPlan(codexContext);
5687
+ if (action === "update") return buildCodexUpdatePlan(codexContext);
5688
+ if (action === "sync") return buildCodexSyncPlan(codexContext);
5689
+ return buildTuiModelPlan(harness, getCodexModelRoles(codexContext));
5690
+ },
5691
+ modelPlan(harness, roles) {
5692
+ return buildTuiModelPlan(harness, roles);
5693
+ },
5694
+ apply(plan) {
5695
+ if (plan.harness === "opencode") return applyOpenCodePlan(plan);
5696
+ if (plan.harness === "claude") return applyClaudeCodePlan(plan);
5697
+ return applyCodexPlan(plan);
5698
+ }
5699
+ };
5700
+
4871
5701
  export {
4872
5702
  claudeCodeAdapter,
4873
5703
  codexAdapter,
4874
5704
  CODEX_ROLE_NAMES,
4875
- parseRoleTomlModel,
5705
+ normalizeEffortSelection,
4876
5706
  buildCodexSetupPlan,
4877
5707
  formatCodexSetupPlan,
4878
5708
  applyCodexSetup,
4879
- CLAUDE_CODE_ROLE_NAMES,
4880
- parseSubagentModel,
4881
5709
  buildClaudeCodeSetupPlan,
4882
5710
  applyClaudeCodeSetup,
4883
5711
  formatClaudeCodeSetupPlan,
4884
5712
  RECOMMENDED_SKILLS,
4885
5713
  installRecommendedSkill,
4886
5714
  getClaudeCodeStatus,
4887
- buildClaudeCodeInstallPlan,
4888
5715
  buildClaudeCodeUpdatePlan,
4889
5716
  buildClaudeCodeSyncPlan,
4890
5717
  buildClaudeCodeModelPlan,
@@ -4893,16 +5720,20 @@ export {
4893
5720
  getCodexStatus,
4894
5721
  buildCodexUpdatePlan,
4895
5722
  buildCodexSyncPlan,
4896
- buildCodexInstallPlan,
4897
5723
  buildCodexModelPlan,
4898
5724
  applyCodexPlan,
4899
5725
  getOpenCodeStatus,
4900
5726
  buildOpenCodeUpdatePlan,
4901
5727
  buildOpenCodeSyncPlan,
4902
- buildOpenCodeInstallPlan,
4903
5728
  buildOpenCodeModelPlan,
4904
5729
  applyOpenCodePlan,
4905
5730
  SUPPORTED_OPERATION_HARNESSES,
4906
5731
  listOperationHarnesses,
4907
- getOperationHarness
5732
+ getOperationHarness,
5733
+ effortChoicesForModel,
5734
+ getModelOptions,
5735
+ getCodexModelRoles,
5736
+ getClaudeCodeModelRoles,
5737
+ getOpenCodeModelRoles,
5738
+ defaultTuiOperations
4908
5739
  };