thoth-agents 0.2.7 → 0.2.9

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,9 +1975,33 @@ 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
  }
2001
+ function normalizeCodexRuntimeModel(model) {
2002
+ const separator = model.indexOf("/");
2003
+ return separator === -1 ? model : model.slice(separator + 1);
2004
+ }
1969
2005
  function applyCodexManagedModelOverrides(config, overrides) {
1970
2006
  if (config.dryRun) {
1971
2007
  return {
@@ -1998,7 +2034,8 @@ function applyCodexManagedModelOverrides(config, overrides) {
1998
2034
  const nextState = {
1999
2035
  version: MANAGED_MODEL_STATE_VERSION,
2000
2036
  models: { ...state.models },
2001
- ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {}
2037
+ ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {},
2038
+ ...state.configuredEfforts ? { configuredEfforts: { ...state.configuredEfforts } } : {}
2002
2039
  };
2003
2040
  try {
2004
2041
  for (const override of overrides) {
@@ -2011,14 +2048,30 @@ function applyCodexManagedModelOverrides(config, overrides) {
2011
2048
  );
2012
2049
  }
2013
2050
  const before = existsSync5(roleItem.targetPath) ? readFileSync5(roleItem.targetPath, "utf8") : roleItem.content;
2014
- const updated = replaceRoleTomlModel(before, override.model);
2051
+ const model = normalizeCodexRuntimeModel(override.model);
2052
+ let updated = replaceRoleTomlModel(before, model);
2053
+ if (override.clearEffort) {
2054
+ updated = replaceRoleTomlEffort(updated, void 0);
2055
+ } else if (override.effort !== void 0) {
2056
+ updated = replaceRoleTomlEffort(updated, override.effort);
2057
+ }
2015
2058
  if (writeTextWithBackup(roleItem.targetPath, updated)) {
2016
2059
  changed.push(roleItem.targetPath);
2017
2060
  }
2018
2061
  const key = roleManagedModelStateKey(roleItem.targetPath);
2019
- nextState.models[key] = parseRoleTomlModel(roleItem.content) ?? override.model;
2062
+ nextState.models[key] = normalizeCodexRuntimeModel(
2063
+ roleItem.renderedModel ?? model
2064
+ );
2020
2065
  nextState.configuredModels ??= {};
2021
- nextState.configuredModels[key] = override.model;
2066
+ nextState.configuredModels[key] = model;
2067
+ if (override.clearEffort) {
2068
+ if (nextState.configuredEfforts) {
2069
+ delete nextState.configuredEfforts[key];
2070
+ }
2071
+ } else if (override.effort !== void 0) {
2072
+ nextState.configuredEfforts ??= {};
2073
+ nextState.configuredEfforts[key] = override.effort;
2074
+ }
2022
2075
  }
2023
2076
  if (writeTextWithBackup(statePath, stableJson3(nextState))) {
2024
2077
  changed.push(statePath);
@@ -2042,6 +2095,11 @@ function resolveRoleTomlContent(options) {
2042
2095
  const key = roleManagedModelStateKey(options.targetPath);
2043
2096
  if (!renderedModel) return options.renderedContent;
2044
2097
  const configuredModel = options.reset ? void 0 : options.state.configuredModels?.[key];
2098
+ const configuredEffort = options.reset ? void 0 : options.state.configuredEfforts?.[key];
2099
+ if (configuredEffort !== void 0) {
2100
+ options.nextState.configuredEfforts ??= {};
2101
+ options.nextState.configuredEfforts[key] = configuredEffort;
2102
+ }
2045
2103
  if (options.reset || !existsSync5(options.targetPath)) {
2046
2104
  options.nextState.models[key] = renderedModel;
2047
2105
  if (configuredModel !== void 0) {
@@ -2051,16 +2109,19 @@ function resolveRoleTomlContent(options) {
2051
2109
  }
2052
2110
  return options.renderedContent;
2053
2111
  }
2054
- const currentModel = parseRoleTomlModel(
2055
- readFileSync5(options.targetPath, "utf8")
2056
- );
2112
+ const installedContent = readFileSync5(options.targetPath, "utf8");
2113
+ const currentModel = parseRoleTomlModel(installedContent);
2114
+ const currentEffort = parseRoleTomlEffort(installedContent);
2057
2115
  if (configuredModel !== void 0) {
2058
2116
  options.nextState.models[key] = renderedModel;
2059
2117
  options.nextState.configuredModels ??= {};
2060
2118
  options.nextState.configuredModels[key] = configuredModel;
2061
- return replaceRoleTomlModel(
2062
- options.renderedContent,
2063
- currentModel && currentModel !== configuredModel ? currentModel : configuredModel
2119
+ return replaceRoleTomlEffort(
2120
+ replaceRoleTomlModel(
2121
+ options.renderedContent,
2122
+ currentModel && currentModel !== configuredModel ? currentModel : configuredModel
2123
+ ),
2124
+ currentEffort
2064
2125
  );
2065
2126
  }
2066
2127
  const trackedModel = options.state.models[key];
@@ -2068,10 +2129,13 @@ function resolveRoleTomlContent(options) {
2068
2129
  if (isUserOwned) {
2069
2130
  if (trackedModel !== void 0)
2070
2131
  options.nextState.models[key] = trackedModel;
2071
- return replaceRoleTomlModel(options.renderedContent, currentModel);
2132
+ return replaceRoleTomlEffort(
2133
+ replaceRoleTomlModel(options.renderedContent, currentModel),
2134
+ currentEffort
2135
+ );
2072
2136
  }
2073
2137
  options.nextState.models[key] = renderedModel;
2074
- return options.renderedContent;
2138
+ return replaceRoleTomlEffort(options.renderedContent, currentEffort);
2075
2139
  }
2076
2140
  function mergePersonalMarketplace(existing, homeDir, personalPluginRoot) {
2077
2141
  const parsed = existing.trim() ? JSON.parse(existing) : {};
@@ -2129,6 +2193,9 @@ function buildCodexSetupPlan(config) {
2129
2193
  description: `Materialize Codex role subagent ${target.role}.`,
2130
2194
  requiresBackup: existsSync5(target.path),
2131
2195
  role: target.role,
2196
+ renderedModel: parseRoleTomlModel(
2197
+ roleArtifactContent(target.role, render.artifacts)
2198
+ ),
2132
2199
  content: resolveRoleTomlContent({
2133
2200
  renderedContent: roleArtifactContent(target.role, render.artifacts),
2134
2201
  targetPath: target.path,
@@ -2445,6 +2512,7 @@ function renderClaudeCodeSubagent(input) {
2445
2512
  `name: ${yamlScalar(input.name)}`,
2446
2513
  `description: ${yamlScalar(input.description)}`,
2447
2514
  `model: ${input.model}`,
2515
+ ...input.effort !== void 0 ? [`effort: ${input.effort}`] : [],
2448
2516
  ...input.tools !== void 0 ? [`tools: ${yamlScalar(input.tools)}`] : [],
2449
2517
  "---"
2450
2518
  ].join("\n");
@@ -2584,25 +2652,25 @@ function stableJson5(value) {
2584
2652
  return `${JSON.stringify(value, null, 2)}
2585
2653
  `;
2586
2654
  }
2587
- function readRootPackageVersion2(context) {
2655
+ function readRootPackageVersion2(context2) {
2588
2656
  const packageJsonPath = findRootPackageJsonPath([
2589
- ...hasPackageRoot(context) ? [context.packageRoot] : [],
2590
- context.projectRoot,
2657
+ ...hasPackageRoot(context2) ? [context2.packageRoot] : [],
2658
+ context2.projectRoot,
2591
2659
  process.cwd(),
2592
2660
  fileURLToPath3(new URL(".", import.meta.url))
2593
2661
  ]);
2594
2662
  return readPackageJsonVersion(packageJsonPath);
2595
2663
  }
2596
- function hasConfig(context) {
2597
- return "config" in context;
2664
+ function hasConfig(context2) {
2665
+ return "config" in context2;
2598
2666
  }
2599
- function hasPackageRoot(context) {
2600
- return "packageRoot" in context && typeof context.packageRoot === "string" && context.packageRoot.length > 0;
2667
+ function hasPackageRoot(context2) {
2668
+ return "packageRoot" in context2 && typeof context2.packageRoot === "string" && context2.packageRoot.length > 0;
2601
2669
  }
2602
- function createPluginManifest(context) {
2670
+ function createPluginManifest(context2) {
2603
2671
  return {
2604
2672
  name: "thoth-agents",
2605
- version: readRootPackageVersion2(context),
2673
+ version: readRootPackageVersion2(context2),
2606
2674
  description: "Delegate-first agent pack with seven roles, thoth-mem persistence, and bundled SDD skills, packaged for Claude Code.",
2607
2675
  author: { name: "thoth-agents" }
2608
2676
  };
@@ -2650,8 +2718,8 @@ var claudeCodeAdapter = {
2650
2718
  id: "claude",
2651
2719
  displayName: "Claude Code",
2652
2720
  capabilities: CLAUDE_CODE_CAPABILITIES,
2653
- render(context) {
2654
- const config = hasConfig(context) ? context.config : void 0;
2721
+ render(context2) {
2722
+ const config = hasConfig(context2) ? context2.config : void 0;
2655
2723
  const componentArtifacts = [
2656
2724
  ...renderSubagentArtifacts(config),
2657
2725
  renderOrchestratorArtifact(config),
@@ -2671,13 +2739,13 @@ var claudeCodeAdapter = {
2671
2739
  }
2672
2740
  ];
2673
2741
  const skillLayout = renderClaudeCodeSkillLayout({
2674
- projectRoot: context.projectRoot,
2675
- ...hasPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
2742
+ projectRoot: context2.projectRoot,
2743
+ ...hasPackageRoot(context2) ? { packageRoot: context2.packageRoot } : {},
2676
2744
  skills: getSkillRegistry()
2677
2745
  });
2678
2746
  componentArtifacts.push(...skillLayout.artifacts);
2679
2747
  const pluginPackage = renderClaudeCodePluginPackage({
2680
- manifest: createPluginManifest(context),
2748
+ manifest: createPluginManifest(context2),
2681
2749
  componentArtifacts
2682
2750
  });
2683
2751
  return {
@@ -2734,6 +2802,19 @@ function replaceSubagentModel(content, model) {
2734
2802
  }
2735
2803
  return content;
2736
2804
  }
2805
+ function parseSubagentEffort(content) {
2806
+ return /^effort:\s*(\S+)\s*$/m.exec(content)?.[1];
2807
+ }
2808
+ function replaceSubagentEffort(content, effort) {
2809
+ if (effort === void 0) {
2810
+ return content.replace(/^effort:\s*\S+\s*\n/m, "");
2811
+ }
2812
+ if (/^effort:\s*\S+\s*$/m.test(content)) {
2813
+ return content.replace(/^effort:\s*\S+\s*$/m, `effort: ${effort}`);
2814
+ }
2815
+ return content.replace(/^(model:\s*\S+\s*)$/m, `$1
2816
+ effort: ${effort}`);
2817
+ }
2737
2818
  function emptyManagedModelState3() {
2738
2819
  return emptyManagedModelState(CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION);
2739
2820
  }
@@ -2769,15 +2850,29 @@ function roleForArtifact(artifact) {
2769
2850
  const name = match?.[1];
2770
2851
  return name && CLAUDE_CODE_ROLE_NAMES.includes(name) ? name : void 0;
2771
2852
  }
2772
- function applyConfiguredModel(content, role, state, nextState, reset) {
2853
+ function applyConfiguredModel(content, targetPath, role, state, nextState, reset) {
2773
2854
  const renderedModel = parseSubagentModel(content);
2774
2855
  if (!role || !renderedModel) return content;
2775
2856
  nextState.models[role] = renderedModel;
2776
2857
  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);
2858
+ let updated = content;
2859
+ if (configured !== void 0) {
2860
+ nextState.configuredModels ??= {};
2861
+ nextState.configuredModels[role] = configured;
2862
+ updated = replaceSubagentModel(updated, configured);
2863
+ }
2864
+ const configuredEffort = reset ? void 0 : state.configuredEfforts?.[role];
2865
+ if (configuredEffort !== void 0) {
2866
+ nextState.configuredEfforts ??= {};
2867
+ nextState.configuredEfforts[role] = configuredEffort;
2868
+ }
2869
+ if (!reset && existsSync6(targetPath)) {
2870
+ const installedEffort = parseSubagentEffort(
2871
+ readFileSync7(targetPath, "utf8")
2872
+ );
2873
+ updated = replaceSubagentEffort(updated, installedEffort);
2874
+ }
2875
+ return updated;
2781
2876
  }
2782
2877
  function buildClaudeCodeSetupPlan(config) {
2783
2878
  const targets = resolveClaudeCodeTargets({
@@ -2795,8 +2890,15 @@ function buildClaudeCodeSetupPlan(config) {
2795
2890
  const items = render.artifacts.map((artifact) => {
2796
2891
  const role = roleForArtifact(artifact);
2797
2892
  const rendered = String(artifact.content ?? "");
2798
- const content = role !== void 0 ? applyConfiguredModel(rendered, role, state, nextState, config.reset) : rendered;
2799
2893
  const targetPath = join8(targets.pluginRoot, artifact.path);
2894
+ const content = role !== void 0 ? applyConfiguredModel(
2895
+ rendered,
2896
+ targetPath,
2897
+ role,
2898
+ state,
2899
+ nextState,
2900
+ config.reset
2901
+ ) : rendered;
2800
2902
  return {
2801
2903
  kind: targetKindForArtifact(artifact),
2802
2904
  action: "write-plugin-file",
@@ -2892,11 +2994,12 @@ function applyClaudeCodeManagedModelOverrides(config, overrides) {
2892
2994
  const nextState = {
2893
2995
  version: CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION,
2894
2996
  models: { ...state.models },
2895
- ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {}
2997
+ ...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {},
2998
+ ...state.configuredEfforts ? { configuredEfforts: { ...state.configuredEfforts } } : {}
2896
2999
  };
2897
3000
  try {
2898
3001
  for (const override of overrides) {
2899
- if (!isClaudeCodeModelAlias(override.model)) {
3002
+ if (!isClaudeCodeModelAlias(override.model) && override.catalogId !== override.model) {
2900
3003
  throw new Error(
2901
3004
  `Unsupported Claude Code model "${override.model}" for ${override.role}; use sonnet, opus, haiku, or inherit.`
2902
3005
  );
@@ -2908,12 +3011,25 @@ function applyClaudeCodeManagedModelOverrides(config, overrides) {
2908
3011
  throw new Error(`Missing Claude Code subagent for ${override.role}.`);
2909
3012
  }
2910
3013
  const before = existsSync6(roleItem.targetPath) ? readFileSync7(roleItem.targetPath, "utf8") : roleItem.content;
2911
- const updated = replaceSubagentModel(before, override.model);
3014
+ let updated = replaceSubagentModel(before, override.model);
3015
+ if (override.clearEffort) {
3016
+ updated = replaceSubagentEffort(updated, void 0);
3017
+ } else if (override.effort !== void 0) {
3018
+ updated = replaceSubagentEffort(updated, override.effort);
3019
+ }
2912
3020
  if (writeTextWithBackup(roleItem.targetPath, updated)) {
2913
3021
  changed.push(roleItem.targetPath);
2914
3022
  }
2915
3023
  nextState.configuredModels ??= {};
2916
3024
  nextState.configuredModels[override.role] = override.model;
3025
+ if (override.clearEffort) {
3026
+ if (nextState.configuredEfforts) {
3027
+ delete nextState.configuredEfforts[override.role];
3028
+ }
3029
+ } else if (override.effort !== void 0) {
3030
+ nextState.configuredEfforts ??= {};
3031
+ nextState.configuredEfforts[override.role] = override.effort;
3032
+ }
2917
3033
  }
2918
3034
  if (writeTextWithBackup(statePath, stableJson3(nextState))) {
2919
3035
  changed.push(statePath);
@@ -2939,6 +3055,31 @@ function formatClaudeCodeSetupPlan(plan) {
2939
3055
  var CLAUDE_CODE_DISPLAY_NAME = "Claude Code";
2940
3056
  var claudeCodePlanSources = /* @__PURE__ */ new WeakMap();
2941
3057
  var claudeCodeModelSources = /* @__PURE__ */ new WeakMap();
3058
+ var CLAUDE_CODE_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
3059
+ function resolveClaudeCodeEffort(input) {
3060
+ if (!input.effort || input.effort.kind === "inherit") {
3061
+ return { ok: true, effort: void 0 };
3062
+ }
3063
+ const effort = input.effort.value;
3064
+ if (!CLAUDE_CODE_EFFORTS.has(effort)) {
3065
+ return {
3066
+ ok: false,
3067
+ code: "claude-code-effort-runtime-unsupported",
3068
+ message: `Claude Code does not support effort ${effort}.`
3069
+ };
3070
+ }
3071
+ if (isClaudeCodeModelAlias(input.model) && input.model !== "inherit") {
3072
+ return { ok: true, effort };
3073
+ }
3074
+ if (input.catalogId === input.model && input.availableEfforts?.includes(effort)) {
3075
+ return { ok: true, effort };
3076
+ }
3077
+ return {
3078
+ ok: false,
3079
+ code: "claude-code-effort-catalog-unsupported",
3080
+ message: `Effort ${effort} is not supported by ${input.catalogId ?? input.model} in the models.dev catalog.`
3081
+ };
3082
+ }
2942
3083
  var claudeCodeActions = [
2943
3084
  {
2944
3085
  id: "claude-code-status",
@@ -3002,16 +3143,16 @@ var claudeCodeOperationAdapter = {
3002
3143
  description: "Claude Code plugin package and managed subagent surfaces.",
3003
3144
  actions: claudeCodeActions
3004
3145
  };
3005
- function claudeCodeConfig(context = { cwd: process.cwd() }, dryRun) {
3146
+ function claudeCodeConfig(context2 = { cwd: process.cwd() }, dryRun) {
3006
3147
  return {
3007
3148
  dryRun,
3008
3149
  reset: false,
3009
3150
  // Match the installer (install.ts uses 'user') so post-install status,
3010
3151
  // 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
3152
+ scope: context2.scope ?? "user",
3153
+ projectRoot: context2.cwd,
3154
+ homeDir: context2.homeDir,
3155
+ packageRoot: context2.packageRoot
3015
3156
  };
3016
3157
  }
3017
3158
  function claudeCodeDisclaimers() {
@@ -3131,10 +3272,10 @@ function statusFromSetupPlan(plan) {
3131
3272
  ]
3132
3273
  };
3133
3274
  }
3134
- function getClaudeCodeStatus(context = { cwd: process.cwd() }) {
3275
+ function getClaudeCodeStatus(context2 = { cwd: process.cwd() }) {
3135
3276
  let plan;
3136
3277
  try {
3137
- plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context, true));
3278
+ plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true));
3138
3279
  } catch (error) {
3139
3280
  const message = error instanceof Error ? error.message : String(error);
3140
3281
  return {
@@ -3192,53 +3333,71 @@ function planFromSetup(id, action, title, summary, setupPlan) {
3192
3333
  claudeCodePlanSources.set(plan, setupPlan);
3193
3334
  return plan;
3194
3335
  }
3195
- function buildClaudeCodeInstallPlan(context = { cwd: process.cwd() }) {
3336
+ function buildClaudeCodeInstallPlan(context2 = { cwd: process.cwd() }) {
3196
3337
  return planFromSetup(
3197
3338
  "claude-code-install-preview",
3198
3339
  "install",
3199
3340
  "Install Claude Code plugin package",
3200
3341
  "Preview Claude Code plugin package install using buildClaudeCodeSetupPlan().",
3201
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3342
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3202
3343
  );
3203
3344
  }
3204
- function buildClaudeCodeUpdatePlan(context = { cwd: process.cwd() }) {
3345
+ function buildClaudeCodeUpdatePlan(context2 = { cwd: process.cwd() }) {
3205
3346
  return planFromSetup(
3206
3347
  "claude-code-update-preview",
3207
3348
  "update",
3208
3349
  "Update Claude Code plugin package",
3209
3350
  "Preview Claude Code managed plugin refresh using buildClaudeCodeSetupPlan().",
3210
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3351
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3211
3352
  );
3212
3353
  }
3213
- function buildClaudeCodeSyncPlan(context = { cwd: process.cwd() }) {
3354
+ function buildClaudeCodeSyncPlan(context2 = { cwd: process.cwd() }) {
3214
3355
  return planFromSetup(
3215
3356
  "claude-code-sync-preview",
3216
3357
  "sync",
3217
3358
  "Sync Claude Code plugin package",
3218
3359
  "Preview Claude Code managed plugin subagents, MCP, hooks, and skills.",
3219
- buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
3360
+ buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true))
3220
3361
  );
3221
3362
  }
3222
3363
  function isClaudeCodeRole(role) {
3223
3364
  return CLAUDE_CODE_ROLE_NAMES.includes(role);
3224
3365
  }
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) => ({
3366
+ function buildClaudeCodeModelPlan(input, context2 = { cwd: process.cwd() }) {
3367
+ const status = getClaudeCodeStatus(context2);
3368
+ const resolvedRoles = input.roles.map((role) => ({
3369
+ role,
3370
+ effort: resolveClaudeCodeEffort(role),
3371
+ validRole: isClaudeCodeRole(role.role),
3372
+ validModel: isClaudeCodeModelAlias(role.model) || role.catalogId === role.model
3373
+ }));
3374
+ const supportedRoles = resolvedRoles.filter((entry) => entry.validRole && entry.validModel && entry.effort.ok).map(({ role, effort }) => ({
3228
3375
  role: role.role,
3229
- model: role.model
3376
+ model: role.model,
3377
+ ...role.catalogId ? { catalogId: role.catalogId } : {},
3378
+ ...effort.ok && effort.effort !== void 0 ? { effort: effort.effort } : {},
3379
+ ...role.effort?.kind === "inherit" ? { clearEffort: true } : {}
3230
3380
  }));
3231
- const rejectedRoles = input.roles.filter(
3232
- (role) => !isClaudeCodeRole(role.role) || !isClaudeCodeModelAlias(role.model)
3381
+ const rejectedRoles = resolvedRoles.filter(
3382
+ (entry) => !entry.validRole || !entry.validModel
3383
+ );
3384
+ const effortErrors = resolvedRoles.filter(
3385
+ (entry) => entry.validRole && entry.validModel && !entry.effort.ok
3233
3386
  );
3234
3387
  const warnings = [
3235
3388
  ...status.diagnostics,
3236
3389
  ...input.warnings ?? [],
3237
3390
  ...rejectedRoles.map(
3238
- (role) => warning(
3391
+ ({ role }) => warning(
3239
3392
  `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
3393
  "claude-code-unsupported-model-role"
3241
3394
  )
3395
+ ),
3396
+ ...effortErrors.map(
3397
+ ({ effort }) => warning(
3398
+ effort.ok ? "Unknown Claude Code effort error." : effort.message,
3399
+ effort.ok ? "claude-code-effort-unknown" : effort.code
3400
+ )
3242
3401
  )
3243
3402
  ];
3244
3403
  if (input.harness !== "claude") {
@@ -3269,7 +3428,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3269
3428
  title: "Configure Claude Code subagent model lines",
3270
3429
  summary: "Preview model changes for generated Claude Code subagent files and managed model state only.",
3271
3430
  dryRun: true,
3272
- canApply: input.harness === "claude" && supportedRoles.length > 0 && status.state !== "unknown",
3431
+ canApply: input.harness === "claude" && supportedRoles.length > 0 && effortErrors.length === 0 && status.state !== "unknown",
3273
3432
  targets: [...targets, ...stateTarget ? [stateTarget] : []],
3274
3433
  surfaces: targets.map((target) => ({
3275
3434
  id: `claude-code-model:${target.label}`,
@@ -3282,7 +3441,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3282
3441
  strategy: "managed-backup-file",
3283
3442
  description: "Existing subagent files and managed model state are backed up by the managed write helper."
3284
3443
  },
3285
- items: supportedRoles.map(({ role, model }) => ({
3444
+ items: supportedRoles.map(({ role, model, effort }) => ({
3286
3445
  title: `Set ${role} Claude Code subagent model line`,
3287
3446
  target: targets.find(
3288
3447
  (target) => target.path?.endsWith(`agents${pathSep()}${role}.md`)
@@ -3290,7 +3449,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3290
3449
  kind: "generated-artifact",
3291
3450
  label: `Claude Code ${role} subagent`
3292
3451
  },
3293
- preview: JSON.stringify({ role, model }),
3452
+ preview: JSON.stringify({ role, model, effort: effort ?? null }),
3294
3453
  backup: { required: true, strategy: "managed-backup-file" }
3295
3454
  })),
3296
3455
  warnings,
@@ -3304,7 +3463,7 @@ function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
3304
3463
  ]
3305
3464
  };
3306
3465
  claudeCodeModelSources.set(plan, {
3307
- config: claudeCodeConfig(context, false),
3466
+ config: claudeCodeConfig(context2, false),
3308
3467
  roles: supportedRoles
3309
3468
  });
3310
3469
  return plan;
@@ -3360,7 +3519,10 @@ function applyClaudeCodePlan(plan) {
3360
3519
  source.config,
3361
3520
  source.roles.map((role) => ({
3362
3521
  role: role.role,
3363
- model: role.model
3522
+ model: role.model,
3523
+ ...role.catalogId ? { catalogId: role.catalogId } : {},
3524
+ ...role.effort ? { effort: role.effort } : {},
3525
+ ...role.clearEffort ? { clearEffort: true } : {}
3364
3526
  }))
3365
3527
  );
3366
3528
  return {
@@ -3479,16 +3641,16 @@ var codexOperationAdapter = {
3479
3641
  description: "Codex agent-pack and managed subagent surfaces.",
3480
3642
  actions: codexActions
3481
3643
  };
3482
- function codexConfig(context = { cwd: process.cwd() }, dryRun) {
3644
+ function codexConfig(context2 = { cwd: process.cwd() }, dryRun) {
3483
3645
  return {
3484
3646
  dryRun,
3485
3647
  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
3648
+ scope: context2.scope ?? "user",
3649
+ projectRoot: context2.cwd,
3650
+ homeDir: context2.homeDir,
3651
+ codexHome: context2.codexHome,
3652
+ packageRoot: context2.packageRoot,
3653
+ pluginId: context2.pluginId
3492
3654
  };
3493
3655
  }
3494
3656
  function codexDisclaimers() {
@@ -3654,10 +3816,10 @@ function statusSummary2(state) {
3654
3816
  return "Codex managed setup could not be classified safely.";
3655
3817
  }
3656
3818
  }
3657
- function getCodexStatus(context = { cwd: process.cwd() }) {
3819
+ function getCodexStatus(context2 = { cwd: process.cwd() }) {
3658
3820
  let plan;
3659
3821
  try {
3660
- plan = buildCodexSetupPlan(codexConfig(context, true));
3822
+ plan = buildCodexSetupPlan(codexConfig(context2, true));
3661
3823
  } catch (error) {
3662
3824
  const message = error instanceof Error ? error.message : String(error);
3663
3825
  return {
@@ -3708,8 +3870,8 @@ function planItemFromSetup2(item) {
3708
3870
  backup: backupForItem2(item)
3709
3871
  };
3710
3872
  }
3711
- function planFromSetup2(id, action, title, summary, setupPlan, context) {
3712
- const status = getCodexStatus(context);
3873
+ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3874
+ const status = getCodexStatus(context2);
3713
3875
  const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated";
3714
3876
  const plan = {
3715
3877
  id,
@@ -3744,54 +3906,91 @@ function planFromSetup2(id, action, title, summary, setupPlan, context) {
3744
3906
  codexPlanSources.set(plan, setupPlan);
3745
3907
  return plan;
3746
3908
  }
3747
- function buildCodexUpdatePlan(context = { cwd: process.cwd() }) {
3748
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3909
+ function buildCodexUpdatePlan(context2 = { cwd: process.cwd() }) {
3910
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3749
3911
  return planFromSetup2(
3750
3912
  "codex-update-preview",
3751
3913
  "update",
3752
3914
  "Update Codex managed setup",
3753
3915
  "Preview Codex managed setup refresh using buildCodexSetupPlan().",
3754
3916
  setupPlan,
3755
- context
3917
+ context2
3756
3918
  );
3757
3919
  }
3758
- function buildCodexSyncPlan(context = { cwd: process.cwd() }) {
3759
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3920
+ function buildCodexSyncPlan(context2 = { cwd: process.cwd() }) {
3921
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3760
3922
  return planFromSetup2(
3761
3923
  "codex-sync-preview",
3762
3924
  "sync",
3763
3925
  "Sync Codex managed configuration",
3764
3926
  "Preview Codex managed root instructions, subagents, plugin source, marketplace entry, and feature gates.",
3765
3927
  setupPlan,
3766
- context
3928
+ context2
3767
3929
  );
3768
3930
  }
3769
- function buildCodexInstallPlan(context = { cwd: process.cwd() }) {
3770
- const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
3931
+ function buildCodexInstallPlan(context2 = { cwd: process.cwd() }) {
3932
+ const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3771
3933
  return planFromSetup2(
3772
3934
  "codex-install-preview",
3773
3935
  "install",
3774
3936
  "Install Codex managed setup",
3775
3937
  "Preview Codex managed agent-pack setup using buildCodexSetupPlan().",
3776
3938
  setupPlan,
3777
- context
3939
+ context2
3778
3940
  );
3779
3941
  }
3780
3942
  function normalizeCodexModel(input) {
3781
- if (input.provider && !input.model.includes("/")) {
3782
- return `${input.provider}/${input.model}`;
3943
+ return normalizeCodexRuntimeModel(input.model);
3944
+ }
3945
+ var CODEX_DOCUMENTED_EFFORTS = /* @__PURE__ */ new Set([
3946
+ "none",
3947
+ "minimal",
3948
+ "low",
3949
+ "medium",
3950
+ "high",
3951
+ "xhigh",
3952
+ "max",
3953
+ "ultra"
3954
+ ]);
3955
+ function resolveCodexEffort(input) {
3956
+ if (!input.effort || input.effort.kind === "inherit") {
3957
+ return { ok: true, value: void 0 };
3958
+ }
3959
+ const value = input.effort.value;
3960
+ if (!CODEX_DOCUMENTED_EFFORTS.has(value)) {
3961
+ return {
3962
+ ok: false,
3963
+ code: "codex-effort-undocumented",
3964
+ message: `Codex does not document reasoning effort "${value}".`
3965
+ };
3783
3966
  }
3784
- return input.model;
3967
+ if (!input.catalogId?.startsWith("openai/") || !input.availableEfforts?.includes(value)) {
3968
+ return {
3969
+ ok: false,
3970
+ code: "codex-effort-model-unsupported",
3971
+ message: `Model ${input.catalogId ?? input.model} does not publish Codex effort "${value}".`
3972
+ };
3973
+ }
3974
+ return { ok: true, value };
3785
3975
  }
3786
3976
  function isCodexRole(role) {
3787
3977
  return CODEX_ROLE_NAMES.includes(role);
3788
3978
  }
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) => ({
3979
+ function buildCodexModelPlan(input, context2 = { cwd: process.cwd() }) {
3980
+ const status = getCodexStatus(context2);
3981
+ const candidateRoles = input.roles.filter((role) => isCodexRole(role.role)).map((role) => ({
3982
+ role: role.role,
3983
+ model: normalizeCodexModel(role),
3984
+ effort: resolveCodexEffort(role),
3985
+ clearEffort: role.effort?.kind === "inherit"
3986
+ }));
3987
+ const supportedRoles = candidateRoles.filter((role) => role.effort.ok).map((role) => ({
3792
3988
  role: role.role,
3793
- model: normalizeCodexModel(role)
3989
+ model: role.model,
3990
+ ...role.effort.value ? { effort: role.effort.value } : {},
3991
+ ...role.clearEffort ? { clearEffort: true } : {}
3794
3992
  }));
3993
+ const effortErrors = candidateRoles.filter((role) => !role.effort.ok);
3795
3994
  const unsupportedRoles = input.roles.filter(
3796
3995
  (role) => !isCodexRole(role.role)
3797
3996
  );
@@ -3803,6 +4002,12 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3803
4002
  `Codex does not expose a supported generated model surface for ${role.role}; this plan will not write that role.`,
3804
4003
  "codex-unsupported-model-role"
3805
4004
  )
4005
+ ),
4006
+ ...effortErrors.map(
4007
+ (role) => warning2(
4008
+ role.effort.message,
4009
+ role.effort.code
4010
+ )
3806
4011
  )
3807
4012
  ];
3808
4013
  if (input.harness !== "codex") {
@@ -3823,7 +4028,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3823
4028
  state: "missing"
3824
4029
  };
3825
4030
  });
3826
- const config = codexConfig(context, false);
4031
+ const config = codexConfig(context2, false);
3827
4032
  const stateTarget = status.targets.find(
3828
4033
  (target) => target.path?.endsWith(".thoth-agents-managed-models.json")
3829
4034
  );
@@ -3834,7 +4039,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3834
4039
  title: "Configure Codex subagent model lines",
3835
4040
  summary: "Preview model changes for generated Codex subagent TOML files and .thoth-agents-managed-models.json only.",
3836
4041
  dryRun: true,
3837
- canApply: input.harness === "codex" && supportedRoles.length > 0 && status.state !== "unknown",
4042
+ canApply: input.harness === "codex" && supportedRoles.length > 0 && effortErrors.length === 0 && status.state !== "unknown",
3838
4043
  targets: [...targets, ...stateTarget ? [stateTarget] : []],
3839
4044
  surfaces: targets.map((target) => ({
3840
4045
  id: `codex-model:${target.label}`,
@@ -3847,7 +4052,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3847
4052
  strategy: "managed-backup-file",
3848
4053
  description: "Existing subagent TOML and managed model state files are backed up by the managed write helper."
3849
4054
  },
3850
- items: supportedRoles.map(({ role, model }) => ({
4055
+ items: supportedRoles.map(({ role, model, effort }) => ({
3851
4056
  title: `Set ${role} Codex subagent model line`,
3852
4057
  target: targets.find(
3853
4058
  (target) => target.path?.endsWith(`thoth-agents-${role}.toml`)
@@ -3855,7 +4060,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
3855
4060
  kind: "generated-artifact",
3856
4061
  label: `Codex ${role} subagent`
3857
4062
  },
3858
- preview: JSON.stringify({ role, model }),
4063
+ preview: JSON.stringify({ role, model, effort }),
3859
4064
  backup: {
3860
4065
  required: true,
3861
4066
  strategy: "managed-backup-file"
@@ -3971,6 +4176,36 @@ function applyCodexPlan(plan) {
3971
4176
  import { existsSync as existsSync10 } from "fs";
3972
4177
  import { join as join10 } from "path";
3973
4178
 
4179
+ // src/cli/opencode-effort.ts
4180
+ var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
4181
+ "none",
4182
+ "low",
4183
+ "medium",
4184
+ "high",
4185
+ "xhigh"
4186
+ ]);
4187
+ function resolveOpenCodeEffort(input) {
4188
+ if (!input.effort || input.effort.kind === "inherit") {
4189
+ return { ok: true, variant: void 0 };
4190
+ }
4191
+ const variant = input.effort.value;
4192
+ if (!input.availableEfforts?.includes(variant)) {
4193
+ return {
4194
+ ok: false,
4195
+ code: "opencode-effort-catalog-unsupported",
4196
+ message: `Effort ${variant} is not supported by ${input.catalogId ?? input.model} in the models.dev catalog.`
4197
+ };
4198
+ }
4199
+ if (input.catalogId?.startsWith("openai/gpt-") && OPENAI_GPT_RUNTIME_VARIANTS.has(variant)) {
4200
+ return { ok: true, variant };
4201
+ }
4202
+ return {
4203
+ ok: false,
4204
+ code: "opencode-effort-runtime-unconfirmed",
4205
+ message: `Effort ${variant} is catalogued for ${input.catalogId ?? input.model}, but the OpenCode runtime mapping is not confirmed.`
4206
+ };
4207
+ }
4208
+
3974
4209
  // src/cli/skills.ts
3975
4210
  import { spawnSync } from "child_process";
3976
4211
  import { existsSync as existsSync9 } from "fs";
@@ -4141,11 +4376,11 @@ function titleCaseSkillName(value) {
4141
4376
  return `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`;
4142
4377
  }).join("-");
4143
4378
  }
4144
- function homeDirFromContext(context) {
4145
- return context.env?.HOME ?? context.env?.USERPROFILE;
4379
+ function homeDirFromContext(context2) {
4380
+ return context2.env?.HOME ?? context2.env?.USERPROFILE;
4146
4381
  }
4147
- function openCodeSkillTargets(context) {
4148
- const homeDir = homeDirFromContext(context);
4382
+ function openCodeSkillTargets(context2) {
4383
+ const homeDir = homeDirFromContext(context2);
4149
4384
  const recommendedTargets = RECOMMENDED_SKILLS.map(
4150
4385
  (skill) => {
4151
4386
  const path4 = getRecommendedSkillPath(skill, homeDir);
@@ -4226,13 +4461,13 @@ function hasSevenAgentPreset(config) {
4226
4461
  function classifyApplySafety(state) {
4227
4462
  return state === "missing" || state === "installed";
4228
4463
  }
4229
- function getOpenCodeStatus(context = { cwd: process.cwd() }) {
4464
+ function getOpenCodeStatus(context2 = { cwd: process.cwd() }) {
4230
4465
  const mainPath = getExistingConfigPath();
4231
4466
  const litePath = getExistingLiteConfigPath();
4232
4467
  const main = parseConfig(mainPath);
4233
4468
  const lite = parseConfig(litePath);
4234
4469
  const diagnostics = [];
4235
- const skillStatus = openCodeSkillTargets(context);
4470
+ const skillStatus = openCodeSkillTargets(context2);
4236
4471
  if (main.error) {
4237
4472
  diagnostics.push({
4238
4473
  severity: "critical",
@@ -4554,11 +4789,26 @@ function normalizeRoleModel(input) {
4554
4789
  function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4555
4790
  const items = input.roles.map((role) => {
4556
4791
  const model = normalizeRoleModel(role);
4792
+ const effort = resolveOpenCodeEffort(role);
4557
4793
  return {
4558
4794
  title: `Set ${role.role} OpenCode model override`,
4559
4795
  target: targetForLiteConfig(),
4560
- preview: JSON.stringify({ [role.role]: { model } }),
4561
- backup: defaultBackup(getExistingLiteConfigPath())
4796
+ preview: JSON.stringify({
4797
+ [role.role]: {
4798
+ model,
4799
+ variant: effort.ok ? effort.variant ?? null : null
4800
+ }
4801
+ }),
4802
+ backup: defaultBackup(getExistingLiteConfigPath()),
4803
+ ...!effort.ok ? {
4804
+ warnings: [
4805
+ {
4806
+ severity: "critical",
4807
+ code: effort.code,
4808
+ message: effort.message
4809
+ }
4810
+ ]
4811
+ } : {}
4562
4812
  };
4563
4813
  });
4564
4814
  const plan = planFromItems(
@@ -4568,6 +4818,11 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4568
4818
  "Preview thoth-agents role model overrides in the plugin config agents map.",
4569
4819
  items
4570
4820
  );
4821
+ const effortWarnings = items.flatMap((item) => item.warnings ?? []);
4822
+ if (effortWarnings.length > 0) {
4823
+ plan.canApply = false;
4824
+ plan.warnings.push(...effortWarnings);
4825
+ }
4571
4826
  if (input.harness !== "opencode") {
4572
4827
  return {
4573
4828
  ...plan,
@@ -4693,13 +4948,14 @@ function applyModelPlan(plan) {
4693
4948
  }
4694
4949
  const role = match[1] ?? "";
4695
4950
  const model = parsed2[role]?.model;
4696
- if (!ROLE_NAMES.includes(role) || typeof model !== "string") {
4951
+ const variant = parsed2[role]?.variant;
4952
+ if (!ROLE_NAMES.includes(role) || typeof model !== "string" || variant !== null && typeof variant !== "string") {
4697
4953
  return rejectPlan3(
4698
4954
  plan,
4699
4955
  "OpenCode model plan contains an invalid role or model."
4700
4956
  );
4701
4957
  }
4702
- roleModels.set(role, model);
4958
+ roleModels.set(role, { model, variant });
4703
4959
  }
4704
4960
  if (roleModels.size === 0) {
4705
4961
  return rejectPlan3(
@@ -4719,14 +4975,44 @@ function applyModelPlan(plan) {
4719
4975
  reset: false
4720
4976
  });
4721
4977
  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)
4978
+ const statePath = getOpenCodeManagedModelStatePath();
4979
+ const state = readManagedModelState(statePath, 1);
4980
+ const configuredEfforts = { ...state.configuredEfforts ?? {} };
4981
+ const warnings = [];
4982
+ for (const [role, override] of roleModels) {
4983
+ const current = {
4984
+ ...agents[role] ?? {}
4726
4985
  };
4986
+ const currentVariant = typeof current.variant === "string" ? current.variant : void 0;
4987
+ const trackedVariant = configuredEfforts[role];
4988
+ const userOwnsVariant = override.variant === null && currentVariant !== void 0 && (trackedVariant === void 0 || currentVariant !== trackedVariant);
4989
+ current.model = override.model;
4990
+ if (userOwnsVariant) {
4991
+ delete configuredEfforts[role];
4992
+ warnings.push({
4993
+ severity: "important",
4994
+ code: "opencode-effort-user-owned",
4995
+ message: `Preserved user-owned OpenCode variant ${currentVariant} for ${role}.`
4996
+ });
4997
+ } else if (override.variant === null) {
4998
+ delete current.variant;
4999
+ delete configuredEfforts[role];
5000
+ } else {
5001
+ current.variant = override.variant;
5002
+ configuredEfforts[role] = override.variant;
5003
+ }
5004
+ agents[role] = current;
4727
5005
  }
4728
5006
  base.agents = agents;
4729
5007
  writeConfig(targetPath, base);
5008
+ writeTextWithBackup(
5009
+ statePath,
5010
+ stableJson3({
5011
+ version: 1,
5012
+ models: state.models,
5013
+ ...Object.keys(configuredEfforts).length > 0 ? { configuredEfforts } : {}
5014
+ })
5015
+ );
4730
5016
  return {
4731
5017
  harness: "opencode",
4732
5018
  action: "model-config",
@@ -4739,7 +5025,7 @@ function applyModelPlan(plan) {
4739
5025
  }
4740
5026
  ],
4741
5027
  backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
4742
- warnings: [],
5028
+ warnings,
4743
5029
  disclaimers: defaultDisclaimers()
4744
5030
  };
4745
5031
  }
@@ -4868,23 +5154,571 @@ function getOperationHarness(harness) {
4868
5154
  return OPERATION_HARNESSES[harness];
4869
5155
  }
4870
5156
 
5157
+ // src/cli/tui/model-catalog.ts
5158
+ import { execFileSync } from "child_process";
5159
+
5160
+ // src/cli/model-catalog/cache.ts
5161
+ import {
5162
+ existsSync as existsSync11,
5163
+ mkdirSync as mkdirSync3,
5164
+ readFileSync as readFileSync10,
5165
+ renameSync as renameSync2,
5166
+ unlinkSync,
5167
+ writeFileSync as writeFileSync3
5168
+ } from "fs";
5169
+ import { homedir as homedir4 } from "os";
5170
+ import { dirname as dirname5, posix, win32 } from "path";
5171
+ import { z as z2 } from "zod";
5172
+ var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
5173
+ var cachedModelOptionSchema = z2.object({
5174
+ id: z2.string().min(1),
5175
+ catalogId: z2.string().min(1).optional(),
5176
+ label: z2.string().min(1),
5177
+ provider: z2.string().min(1),
5178
+ efforts: z2.array(z2.string().min(1)),
5179
+ source: modelCatalogSourceSchema
5180
+ }).passthrough();
5181
+ var modelsDevCacheSchema = z2.object({
5182
+ version: z2.literal(1),
5183
+ etag: z2.string().optional(),
5184
+ fetchedAt: z2.string().datetime(),
5185
+ catalog: z2.object({ models: z2.array(cachedModelOptionSchema) }).passthrough()
5186
+ }).passthrough();
5187
+ function resolveModelsDevCachePath(options = {}) {
5188
+ const platform = options.platform ?? process.platform;
5189
+ const env = options.env ?? process.env;
5190
+ const home = options.homeDir ?? homedir4();
5191
+ if (platform === "win32") {
5192
+ const path4 = win32;
5193
+ const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path4.join(home, "AppData", "Local");
5194
+ return path4.join(base2, "thoth-agents", "models-dev-v1.json");
5195
+ }
5196
+ const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
5197
+ return posix.join(base, "thoth-agents", "models-dev-v1.json");
5198
+ }
5199
+ function readModelsDevCache(cachePath) {
5200
+ if (!existsSync11(cachePath)) return void 0;
5201
+ try {
5202
+ const input = JSON.parse(readFileSync10(cachePath, "utf8"));
5203
+ const parsed = modelsDevCacheSchema.safeParse(input);
5204
+ return parsed.success ? parsed.data : void 0;
5205
+ } catch {
5206
+ return void 0;
5207
+ }
5208
+ }
5209
+ function writeModelsDevCacheAtomic(cachePath, cache) {
5210
+ const validated = modelsDevCacheSchema.safeParse(cache);
5211
+ if (!validated.success) {
5212
+ throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
5213
+ }
5214
+ mkdirSync3(dirname5(cachePath), { recursive: true });
5215
+ const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
5216
+ try {
5217
+ writeFileSync3(
5218
+ temporaryPath,
5219
+ `${JSON.stringify(validated.data, null, 2)}
5220
+ `
5221
+ );
5222
+ renameSync2(temporaryPath, cachePath);
5223
+ } catch (error) {
5224
+ if (existsSync11(temporaryPath)) unlinkSync(temporaryPath);
5225
+ throw error;
5226
+ }
5227
+ }
5228
+
5229
+ // src/cli/model-catalog/models-dev.ts
5230
+ import { z as z3 } from "zod";
5231
+ var modelsDevModelSchema = z3.object({
5232
+ id: z3.string().min(1).optional(),
5233
+ name: z3.string().min(1).optional(),
5234
+ reasoning: z3.boolean().optional(),
5235
+ reasoning_options: z3.array(z3.unknown()).optional()
5236
+ }).passthrough();
5237
+ var modelsDevProviderSchema = z3.object({
5238
+ models: z3.record(z3.string(), modelsDevModelSchema)
5239
+ }).passthrough();
5240
+ var modelsDevCatalogSchema = z3.record(
5241
+ z3.string(),
5242
+ modelsDevProviderSchema
5243
+ );
5244
+ var effortOptionSchema = z3.object({
5245
+ type: z3.literal("effort"),
5246
+ values: z3.array(z3.string().min(1))
5247
+ }).passthrough();
5248
+ function normalizedCatalogId(provider, modelKey, declaredId) {
5249
+ const candidate = (declaredId ?? modelKey).trim().toLowerCase();
5250
+ return candidate.includes("/") ? candidate : `${provider}/${candidate}`;
5251
+ }
5252
+ function runtimeModelId(provider, catalogId) {
5253
+ const prefix = `${provider}/`;
5254
+ return catalogId.startsWith(prefix) ? catalogId.slice(prefix.length) : catalogId;
5255
+ }
5256
+ function effortValues(options) {
5257
+ const values = /* @__PURE__ */ new Set();
5258
+ for (const option of options ?? []) {
5259
+ const parsed = effortOptionSchema.safeParse(option);
5260
+ if (!parsed.success) continue;
5261
+ for (const value of parsed.data.values) {
5262
+ const normalized = value.trim();
5263
+ if (normalized) values.add(normalized);
5264
+ }
5265
+ }
5266
+ return [...values];
5267
+ }
5268
+ function normalizeModelsDevCatalog(input) {
5269
+ const parsed = modelsDevCatalogSchema.safeParse(input);
5270
+ if (!parsed.success) {
5271
+ return {
5272
+ ok: false,
5273
+ issues: parsed.error.issues.map(
5274
+ (issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`
5275
+ )
5276
+ };
5277
+ }
5278
+ const models = [];
5279
+ for (const [rawProvider, provider] of Object.entries(parsed.data)) {
5280
+ const providerId = rawProvider.trim().toLowerCase();
5281
+ for (const [modelKey, model] of Object.entries(provider.models)) {
5282
+ const catalogId = normalizedCatalogId(providerId, modelKey, model.id);
5283
+ const id = runtimeModelId(providerId, catalogId);
5284
+ models.push({
5285
+ id,
5286
+ catalogId,
5287
+ label: model.name ?? id,
5288
+ provider: providerId,
5289
+ efforts: effortValues(model.reasoning_options),
5290
+ source: "remote"
5291
+ });
5292
+ }
5293
+ }
5294
+ return { ok: true, catalog: { models } };
5295
+ }
5296
+
5297
+ // src/cli/model-catalog/index.ts
5298
+ var MODELS_DEV_URL = "https://models.dev/api.json";
5299
+ var MODELS_DEV_MAX_BYTES = 8 * 1024 * 1024;
5300
+ var MODELS_DEV_TIMEOUT_MS = 5e3;
5301
+ function ownsCatalogKey(model, key) {
5302
+ const separator = key.indexOf("/");
5303
+ return separator > 0 && model.provider === key.slice(0, separator);
5304
+ }
5305
+ function mergeModelCatalog(dynamic, manual) {
5306
+ const merged = /* @__PURE__ */ new Map();
5307
+ for (const model of dynamic) {
5308
+ const key = model.catalogId ?? model.id;
5309
+ const existing = merged.get(key);
5310
+ if (existing && ownsCatalogKey(existing, key) && !ownsCatalogKey(model, key)) {
5311
+ continue;
5312
+ }
5313
+ merged.set(key, model);
5314
+ }
5315
+ for (const model of manual) {
5316
+ const key = model.catalogId ?? model.id;
5317
+ if (!merged.has(key)) merged.set(key, model);
5318
+ }
5319
+ return [...merged.values()];
5320
+ }
5321
+ function lkgModels(models) {
5322
+ return models.map((model) => ({
5323
+ ...model,
5324
+ efforts: [...model.efforts],
5325
+ source: "lkg"
5326
+ }));
5327
+ }
5328
+ async function loadModelsDevCatalog(options = {}) {
5329
+ const cachePath = options.cachePath ?? resolveModelsDevCachePath();
5330
+ const manual = options.manual ?? [];
5331
+ const fetcher = options.fetcher ?? fetch;
5332
+ const checkedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
5333
+ const cached = readModelsDevCache(cachePath);
5334
+ const warnings = [];
5335
+ try {
5336
+ const headers = new Headers({ Accept: "application/json" });
5337
+ if (cached?.etag) headers.set("If-None-Match", cached.etag);
5338
+ const response = await fetcher(MODELS_DEV_URL, {
5339
+ headers,
5340
+ signal: AbortSignal.timeout(MODELS_DEV_TIMEOUT_MS)
5341
+ });
5342
+ if (response.status === 304) {
5343
+ if (!cached)
5344
+ throw new Error("models.dev returned 304 without an LKG cache");
5345
+ return {
5346
+ models: mergeModelCatalog(lkgModels(cached.catalog.models), manual),
5347
+ source: "lkg",
5348
+ stale: false,
5349
+ checkedAt,
5350
+ warnings
5351
+ };
5352
+ }
5353
+ if (!response.ok)
5354
+ throw new Error(`models.dev returned HTTP ${response.status}`);
5355
+ const text = await response.text();
5356
+ if (Buffer.byteLength(text, "utf8") > MODELS_DEV_MAX_BYTES) {
5357
+ throw new Error("models.dev response exceeded the maximum size");
5358
+ }
5359
+ const input = JSON.parse(text);
5360
+ const normalized = normalizeModelsDevCatalog(input);
5361
+ if (!normalized.ok) {
5362
+ throw new Error(
5363
+ `models.dev validation failed: ${normalized.issues.join("; ")}`
5364
+ );
5365
+ }
5366
+ const remoteModels = normalized.catalog.models.map((model) => ({
5367
+ ...model,
5368
+ source: "remote"
5369
+ }));
5370
+ writeModelsDevCacheAtomic(cachePath, {
5371
+ version: 1,
5372
+ ...response.headers.get("etag") ? { etag: response.headers.get("etag") ?? void 0 } : {},
5373
+ fetchedAt: checkedAt,
5374
+ catalog: { models: lkgModels(remoteModels) }
5375
+ });
5376
+ return {
5377
+ models: mergeModelCatalog(remoteModels, manual),
5378
+ source: "remote",
5379
+ stale: false,
5380
+ checkedAt,
5381
+ warnings
5382
+ };
5383
+ } catch (error) {
5384
+ warnings.push(error instanceof Error ? error.message : String(error));
5385
+ }
5386
+ if (cached) {
5387
+ return {
5388
+ models: mergeModelCatalog(lkgModels(cached.catalog.models), manual),
5389
+ source: "lkg",
5390
+ stale: true,
5391
+ checkedAt,
5392
+ warnings
5393
+ };
5394
+ }
5395
+ return {
5396
+ models: [...manual],
5397
+ source: "manual",
5398
+ stale: true,
5399
+ checkedAt,
5400
+ warnings
5401
+ };
5402
+ }
5403
+
5404
+ // src/cli/tui/model-catalog.ts
5405
+ var MODEL_CATALOG_TIMEOUT_MS = 5e3;
5406
+ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
5407
+ "none",
5408
+ "minimal",
5409
+ "low",
5410
+ "medium",
5411
+ "high",
5412
+ "xhigh",
5413
+ "max",
5414
+ "ultra"
5415
+ ]);
5416
+ var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
5417
+ function parseOpenCodeModels(output) {
5418
+ const seen = /* @__PURE__ */ new Set();
5419
+ const options = [];
5420
+ for (const line of output.split(/\r?\n/)) {
5421
+ const match = line.trim().match(/^([a-z0-9_-]+)\/([^\s{]+)/i);
5422
+ if (!match) continue;
5423
+ const id = `${match[1]}/${match[2]}`;
5424
+ if (seen.has(id)) continue;
5425
+ seen.add(id);
5426
+ options.push({
5427
+ id,
5428
+ catalogId: id,
5429
+ label: id,
5430
+ provider: match[1] ?? "unknown",
5431
+ efforts: [],
5432
+ source: "manual"
5433
+ });
5434
+ }
5435
+ return options;
5436
+ }
5437
+ function isCodexOpenAiModelId(id) {
5438
+ const match = id.match(/^gpt-(\d+)(?:[.-]|$)/);
5439
+ return match?.[1] !== void 0 && Number(match[1]) >= 5;
5440
+ }
5441
+ function writableEfforts(harness, option) {
5442
+ if (harness === "codex") {
5443
+ return option.efforts.filter(
5444
+ (effort) => CODEX_DOCUMENTED_EFFORTS2.has(effort)
5445
+ );
5446
+ }
5447
+ if (harness === "claude") {
5448
+ return option.efforts.filter(
5449
+ (effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
5450
+ );
5451
+ }
5452
+ return option.efforts.filter(
5453
+ (effort) => resolveOpenCodeEffort({
5454
+ model: option.id,
5455
+ catalogId: option.catalogId,
5456
+ availableEfforts: option.efforts,
5457
+ effort: { kind: "effort", value: effort }
5458
+ }).ok
5459
+ );
5460
+ }
5461
+ function effortChoicesForModel(option) {
5462
+ return ["inherit", ...option?.efforts ?? []];
5463
+ }
5464
+ function getOpenCodeModelsInvocation(platform = process.platform) {
5465
+ const options = {
5466
+ encoding: "utf8",
5467
+ stdio: ["ignore", "pipe", "ignore"],
5468
+ timeout: MODEL_CATALOG_TIMEOUT_MS
5469
+ };
5470
+ if (platform === "win32") {
5471
+ return {
5472
+ command: "opencode models",
5473
+ args: [],
5474
+ options: { ...options, shell: true }
5475
+ };
5476
+ }
5477
+ return { command: "opencode", args: ["models"], options };
5478
+ }
5479
+ function getNativeOpenCodeOptions() {
5480
+ try {
5481
+ const invocation = getOpenCodeModelsInvocation();
5482
+ return parseOpenCodeModels(
5483
+ execFileSync(invocation.command, invocation.args, invocation.options)
5484
+ );
5485
+ } catch {
5486
+ return [];
5487
+ }
5488
+ }
5489
+ var CLAUDE_CODE_MODEL_OPTIONS = [
5490
+ { id: "sonnet", label: "sonnet", provider: "anthropic" },
5491
+ { id: "opus", label: "opus", provider: "anthropic" },
5492
+ { id: "haiku", label: "haiku", provider: "anthropic" },
5493
+ {
5494
+ id: "inherit",
5495
+ label: "inherit (main session model)",
5496
+ provider: "anthropic"
5497
+ }
5498
+ ].map((option) => ({
5499
+ ...option,
5500
+ efforts: option.id === "inherit" ? [] : [...CLAUDE_CODE_EFFORTS2],
5501
+ source: "manual"
5502
+ }));
5503
+ async function getModelOptions(harness) {
5504
+ const native = harness === "opencode" ? getNativeOpenCodeOptions() : [];
5505
+ const loaded = await loadModelsDevCatalog({ manual: native });
5506
+ if (harness === "claude") {
5507
+ const aliases = CLAUDE_CODE_MODEL_OPTIONS.map((option) => ({
5508
+ ...option,
5509
+ efforts: [...option.efforts]
5510
+ }));
5511
+ const concrete = loaded.models.filter((option) => option.provider === "anthropic").map((option) => ({
5512
+ ...option,
5513
+ id: option.catalogId ?? option.id,
5514
+ efforts: writableEfforts(harness, option)
5515
+ }));
5516
+ return [...aliases, ...concrete];
5517
+ }
5518
+ if (harness === "codex") {
5519
+ return loaded.models.filter(
5520
+ (option) => option.provider === "openai" && isCodexOpenAiModelId(option.id)
5521
+ ).map((option) => ({
5522
+ ...option,
5523
+ efforts: writableEfforts(harness, option)
5524
+ }));
5525
+ }
5526
+ const catalogById = new Map(
5527
+ loaded.models.map((option) => [option.catalogId ?? option.id, option])
5528
+ );
5529
+ return native.map((option) => {
5530
+ const catalog = catalogById.get(option.catalogId ?? option.id);
5531
+ const enriched = catalog ? { ...catalog, id: option.id, catalogId: option.catalogId } : option;
5532
+ return { ...enriched, efforts: writableEfforts(harness, enriched) };
5533
+ });
5534
+ }
5535
+
5536
+ // src/cli/tui/operations.ts
5537
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
5538
+ var context = { cwd: process.cwd() };
5539
+ var codexContext = { cwd: process.cwd() };
5540
+ var claudeCodeContext = {
5541
+ cwd: process.cwd(),
5542
+ scope: "user"
5543
+ };
5544
+ var opencodeModelRoles = ALL_AGENT_NAMES.map(
5545
+ (role) => ({
5546
+ role,
5547
+ model: DEFAULT_MODELS[role] ?? "openai/gpt-5.4"
5548
+ })
5549
+ );
5550
+ var codexModelRoles = CODEX_ROLE_NAMES.map(
5551
+ (role) => ({
5552
+ role,
5553
+ model: "gpt-5.4-mini"
5554
+ })
5555
+ );
5556
+ var codexDefaultModels = new Map(
5557
+ codexModelRoles.map((role) => [role.role, role.model])
5558
+ );
5559
+ function codexInstallConfig(source, dryRun) {
5560
+ return {
5561
+ dryRun,
5562
+ reset: false,
5563
+ scope: source.scope ?? "user",
5564
+ projectRoot: source.cwd,
5565
+ homeDir: source.homeDir,
5566
+ codexHome: source.codexHome,
5567
+ packageRoot: source.packageRoot,
5568
+ pluginId: source.pluginId
5569
+ };
5570
+ }
5571
+ function getCodexModelRoles(source = codexContext) {
5572
+ try {
5573
+ const plan = buildCodexSetupPlan(codexInstallConfig(source, true));
5574
+ return CODEX_ROLE_NAMES.map((role) => {
5575
+ const item = plan.items.find(
5576
+ (candidate) => candidate.action === "write-role-toml" && candidate.role === role
5577
+ );
5578
+ const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
5579
+ const effort = content ? parseRoleTomlEffort(content) : void 0;
5580
+ return {
5581
+ role,
5582
+ model: (content ? parseRoleTomlModel(content) : void 0) ?? codexDefaultModels.get(role) ?? "gpt-5.4-mini",
5583
+ effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
5584
+ };
5585
+ });
5586
+ } catch {
5587
+ return codexModelRoles.map((role) => ({ ...role }));
5588
+ }
5589
+ }
5590
+ function getClaudeCodeModelRoles(source = claudeCodeContext) {
5591
+ try {
5592
+ const plan = buildClaudeCodeSetupPlan({
5593
+ dryRun: true,
5594
+ reset: false,
5595
+ scope: source.scope ?? "user",
5596
+ projectRoot: source.cwd,
5597
+ homeDir: source.homeDir,
5598
+ packageRoot: source.packageRoot
5599
+ });
5600
+ return CLAUDE_CODE_ROLE_NAMES.map((role) => {
5601
+ const item = plan.items.find(
5602
+ (candidate) => candidate.kind === "subagent" && candidate.role === role
5603
+ );
5604
+ const content = item && existsSync12(item.targetPath) ? readFileSync11(item.targetPath, "utf8") : item?.content;
5605
+ const model = content ? parseSubagentModel(content) : void 0;
5606
+ const effort = content ? parseSubagentEffort(content) : void 0;
5607
+ return {
5608
+ role,
5609
+ model: model ?? "inherit",
5610
+ effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
5611
+ };
5612
+ });
5613
+ } catch {
5614
+ return defaultClaudeCodeModelRoles();
5615
+ }
5616
+ }
5617
+ function readRoleField(config, role, field) {
5618
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
5619
+ return void 0;
5620
+ }
5621
+ const record = config;
5622
+ const value = record[role];
5623
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5624
+ return void 0;
5625
+ }
5626
+ const fieldValue = value[field];
5627
+ return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : void 0;
5628
+ }
5629
+ function getOpenCodeModelRoles() {
5630
+ const parsed = parseConfig(getExistingLiteConfigPath());
5631
+ const agents = parsed.config?.agents && typeof parsed.config.agents === "object" ? parsed.config.agents : void 0;
5632
+ const presets = parsed.config?.presets && typeof parsed.config.presets === "object" ? parsed.config.presets : {};
5633
+ const openaiPreset = presets.openai && typeof presets.openai === "object" ? presets.openai : void 0;
5634
+ return ALL_AGENT_NAMES.map((role) => {
5635
+ const variant = readRoleField(agents, role, "variant") ?? readRoleField(openaiPreset, role, "variant");
5636
+ return {
5637
+ role,
5638
+ model: readRoleField(agents, role, "model") ?? readRoleField(openaiPreset, role, "model") ?? DEFAULT_MODELS[role] ?? "openai/gpt-5.4",
5639
+ effort: variant ? { kind: "effort", value: variant } : { kind: "inherit" }
5640
+ };
5641
+ });
5642
+ }
5643
+ function buildTuiModelPlan(harness, roles) {
5644
+ if (harness === "opencode") {
5645
+ return buildOpenCodeModelPlan({ harness, dryRun: true, roles }, context);
5646
+ }
5647
+ if (harness === "claude") {
5648
+ return buildClaudeCodeModelPlan(
5649
+ { harness, dryRun: true, roles },
5650
+ claudeCodeContext
5651
+ );
5652
+ }
5653
+ return buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
5654
+ }
5655
+ var defaultTuiOperations = {
5656
+ status(harness) {
5657
+ if (harness === "opencode") return getOpenCodeStatus(context);
5658
+ if (harness === "claude") {
5659
+ return getClaudeCodeStatus(claudeCodeContext);
5660
+ }
5661
+ return getCodexStatus(codexContext);
5662
+ },
5663
+ modelRoles(harness) {
5664
+ if (harness === "opencode") return getOpenCodeModelRoles();
5665
+ if (harness === "claude") {
5666
+ return getClaudeCodeModelRoles(claudeCodeContext);
5667
+ }
5668
+ return getCodexModelRoles(codexContext);
5669
+ },
5670
+ modelOptions(harness) {
5671
+ return getModelOptions(harness);
5672
+ },
5673
+ plan(harness, action) {
5674
+ if (harness === "opencode") {
5675
+ if (action === "install") return buildOpenCodeInstallPlan(context);
5676
+ if (action === "update") return buildOpenCodeUpdatePlan(context);
5677
+ if (action === "sync") return buildOpenCodeSyncPlan(context);
5678
+ return buildTuiModelPlan(harness, getOpenCodeModelRoles());
5679
+ }
5680
+ if (harness === "claude") {
5681
+ if (action === "install") {
5682
+ return buildClaudeCodeInstallPlan(claudeCodeContext);
5683
+ }
5684
+ if (action === "update") {
5685
+ return buildClaudeCodeUpdatePlan(claudeCodeContext);
5686
+ }
5687
+ if (action === "sync") return buildClaudeCodeSyncPlan(claudeCodeContext);
5688
+ return buildTuiModelPlan(
5689
+ harness,
5690
+ getClaudeCodeModelRoles(claudeCodeContext)
5691
+ );
5692
+ }
5693
+ if (action === "install") return buildCodexInstallPlan(codexContext);
5694
+ if (action === "update") return buildCodexUpdatePlan(codexContext);
5695
+ if (action === "sync") return buildCodexSyncPlan(codexContext);
5696
+ return buildTuiModelPlan(harness, getCodexModelRoles(codexContext));
5697
+ },
5698
+ modelPlan(harness, roles) {
5699
+ return buildTuiModelPlan(harness, roles);
5700
+ },
5701
+ apply(plan) {
5702
+ if (plan.harness === "opencode") return applyOpenCodePlan(plan);
5703
+ if (plan.harness === "claude") return applyClaudeCodePlan(plan);
5704
+ return applyCodexPlan(plan);
5705
+ }
5706
+ };
5707
+
4871
5708
  export {
4872
5709
  claudeCodeAdapter,
4873
5710
  codexAdapter,
4874
5711
  CODEX_ROLE_NAMES,
4875
- parseRoleTomlModel,
5712
+ normalizeEffortSelection,
4876
5713
  buildCodexSetupPlan,
4877
5714
  formatCodexSetupPlan,
4878
5715
  applyCodexSetup,
4879
- CLAUDE_CODE_ROLE_NAMES,
4880
- parseSubagentModel,
4881
5716
  buildClaudeCodeSetupPlan,
4882
5717
  applyClaudeCodeSetup,
4883
5718
  formatClaudeCodeSetupPlan,
4884
5719
  RECOMMENDED_SKILLS,
4885
5720
  installRecommendedSkill,
4886
5721
  getClaudeCodeStatus,
4887
- buildClaudeCodeInstallPlan,
4888
5722
  buildClaudeCodeUpdatePlan,
4889
5723
  buildClaudeCodeSyncPlan,
4890
5724
  buildClaudeCodeModelPlan,
@@ -4893,16 +5727,20 @@ export {
4893
5727
  getCodexStatus,
4894
5728
  buildCodexUpdatePlan,
4895
5729
  buildCodexSyncPlan,
4896
- buildCodexInstallPlan,
4897
5730
  buildCodexModelPlan,
4898
5731
  applyCodexPlan,
4899
5732
  getOpenCodeStatus,
4900
5733
  buildOpenCodeUpdatePlan,
4901
5734
  buildOpenCodeSyncPlan,
4902
- buildOpenCodeInstallPlan,
4903
5735
  buildOpenCodeModelPlan,
4904
5736
  applyOpenCodePlan,
4905
5737
  SUPPORTED_OPERATION_HARNESSES,
4906
5738
  listOperationHarnesses,
4907
- getOperationHarness
5739
+ getOperationHarness,
5740
+ effortChoicesForModel,
5741
+ getModelOptions,
5742
+ getCodexModelRoles,
5743
+ getClaudeCodeModelRoles,
5744
+ getOpenCodeModelRoles,
5745
+ defaultTuiOperations
4908
5746
  };