thoth-agents 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +11 -2
  2. package/dist/{chunk-SEF6PFM6.js → chunk-5MYXVRZZ.js} +132 -157
  3. package/dist/chunk-6O4GKDS7.js +207 -0
  4. package/dist/{chunk-QOA2AETX.js → chunk-NOLDFT7O.js} +3 -3
  5. package/dist/{chunk-7F33WUSD.js → chunk-VVYAQTYE.js} +2 -2
  6. package/dist/{chunk-NY2IPJ7V.js → chunk-YCWGTDFE.js} +531 -233
  7. package/dist/cli/index.js +6 -6
  8. package/dist/cli/opencode-profiles.d.ts +10 -0
  9. package/dist/cli/tui/index.js +3 -3
  10. package/dist/config/loader.d.ts +1 -4
  11. package/dist/config/schema.d.ts +0 -22
  12. package/dist/index.js +824 -30
  13. package/dist/opencode/context.d.ts +16 -0
  14. package/dist/opencode/profile-rpc-handlers.d.ts +3 -0
  15. package/dist/opencode/profile-rpc.d.ts +688 -0
  16. package/dist/opencode/profile-runtime.d.ts +3 -0
  17. package/dist/opencode/profile-sessions.d.ts +18 -0
  18. package/dist/opencode/profiles/editor.d.ts +44 -0
  19. package/dist/opencode/profiles/index.d.ts +18 -0
  20. package/dist/opencode/profiles/resolver.d.ts +12 -0
  21. package/dist/opencode/profiles/store.d.ts +63 -0
  22. package/dist/opencode/profiles/types.d.ts +26 -0
  23. package/dist/opencode/tool-overrides/evaluator.d.ts +13 -0
  24. package/dist/opencode/tool-overrides/index.d.ts +5 -0
  25. package/dist/opencode/tool-overrides/store.d.ts +38 -0
  26. package/dist/opencode/tool-overrides/types.d.ts +32 -0
  27. package/dist/opencode/tool-overrides/validation.d.ts +5 -0
  28. package/dist/opencode/tool-overrides/wildcard.d.ts +3 -0
  29. package/dist/opencode/tool-rpc-handlers.d.ts +3 -0
  30. package/dist/opencode/tool-rpc.d.ts +428 -0
  31. package/dist/pi.js +2 -2
  32. package/dist/tui/fixtures.d.ts +2 -0
  33. package/dist/tui/flow.d.ts +16 -0
  34. package/dist/tui/index.d.ts +7 -0
  35. package/dist/tui/index.js +1384 -0
  36. package/dist/tui/model.d.ts +84 -0
  37. package/dist/tui/tools/fixtures.d.ts +3 -0
  38. package/dist/tui/tools/flow.d.ts +11 -0
  39. package/dist/tui/tools/model.d.ts +97 -0
  40. package/package.json +5 -1
  41. package/plugin/.claude-plugin/plugin.json +1 -1
  42. package/plugin/.codex-plugin/plugin.json +1 -1
  43. package/thoth-agents.schema.json +0 -70
package/README.md CHANGED
@@ -291,8 +291,17 @@ npx thoth-agents@latest
291
291
  ```
292
292
 
293
293
  Choose models your harness and provider account can access. OpenCode ships the
294
- **OpenAI preset**; per-role overrides let you customize it. Other harnesses use
295
- their own model configuration and capability rules.
294
+ **`openai` model profile**. Run `/subagents-model` inside OpenCode to edit named
295
+ model/variant profiles for all six roles, including orchestrator. Selection is
296
+ session → project pin → global active → read-only `default`. Profiles live in
297
+ `<opencode-config-dir>/thoth/profiles.json` and
298
+ `<project>/.opencode/thoth/profiles.json`; the CLI manages global profiles only.
299
+ Other harnesses use their own model configuration and capability rules.
300
+
301
+ In OpenCode, `/subagents-tools` edits global/project allow/ask/deny overrides for
302
+ the five specialists, independently of model profiles. Project entries replace
303
+ global entries per role; `subagent` stays locked. See the
304
+ [tool-permission guide and limits](docs/provider-configurations.md#manage-tool-permissions-inside-opencode).
296
305
 
297
306
  See [Provider Configuration](docs/provider-configurations.md) and
298
307
  [Codex Model Customization](docs/codex-model-customization.md).
@@ -17,7 +17,7 @@ import {
17
17
  savePiModelConfig,
18
18
  syncPiSpecialists,
19
19
  writePiManagedText
20
- } from "./chunk-7F33WUSD.js";
20
+ } from "./chunk-VVYAQTYE.js";
21
21
  import {
22
22
  ALL_AGENT_NAMES,
23
23
  CLAUDE_CODE_PROMPT_DIALECT,
@@ -26,9 +26,12 @@ import {
26
26
  CONFIRMED_OPENAI_SUBAGENT_PRESET,
27
27
  DEFAULT_MODELS,
28
28
  LEGACY_OPENAI_PRESET,
29
+ MODEL_MAPPINGS,
29
30
  PI_SPECIALIST_ROLES,
30
31
  SUBAGENT_NAMES,
32
+ activeOpenCodeProfile,
31
33
  claudeCodeSubagentType,
34
+ effectiveOpenCodeProfile,
32
35
  ensureConfigDir,
33
36
  findPackageRoot,
34
37
  generateLiteConfig,
@@ -44,15 +47,17 @@ import {
44
47
  isManagedOpenCodePlugin,
45
48
  isPiSpecialistRole,
46
49
  isValidPackageVersion,
50
+ openCodeProfileStore,
47
51
  parseConfig,
48
52
  piSpecialistName,
49
53
  pluginEntries,
50
54
  renderAgentRoutingDescription,
51
55
  resolveExecutingPackageVersion,
52
56
  updateOpenCodeMainConfig,
57
+ withoutLegacyPresets,
53
58
  writeConfig,
54
59
  writeLiteConfig
55
- } from "./chunk-NY2IPJ7V.js";
60
+ } from "./chunk-YCWGTDFE.js";
56
61
 
57
62
  // src/cli/model-effort.ts
58
63
  function normalizeEffortSelection(value) {
@@ -6880,8 +6885,8 @@ function getOpenCodePath() {
6880
6885
  var PACKAGE_NAME = "thoth-agents";
6881
6886
  var EXECUTING_PACKAGE_VERSION = resolveExecutingPackageVersion();
6882
6887
  var EXPECTED_PLUGIN = EXECUTING_PACKAGE_VERSION.ok ? `${PACKAGE_NAME}@${EXECUTING_PACKAGE_VERSION.version}` : null;
6883
- var OPENAI_PRESET = "openai";
6884
- var APPLIED_MODELS_PRESET = "agents";
6888
+ var OPENAI_PROFILE = "openai";
6889
+ var APPLIED_MODELS_PROFILE = "agents";
6885
6890
  var ROLE_NAMES = [...ALL_AGENT_NAMES];
6886
6891
  var issuedOpenCodePlans = /* @__PURE__ */ new WeakMap();
6887
6892
  var NonCanonicalDataError = class extends Error {
@@ -7103,7 +7108,26 @@ function targetForLiteConfig(state) {
7103
7108
  path: getExistingLiteConfigPath(),
7104
7109
  label: "thoth-agents config",
7105
7110
  ...state ? { state } : {},
7106
- expected: `six-role ${OPENAI_PRESET} roster`
7111
+ expected: `six-role ${OPENAI_PROFILE} roster`
7112
+ };
7113
+ }
7114
+ function targetForProfiles() {
7115
+ return {
7116
+ kind: "config",
7117
+ path: openCodeProfileStore().paths.global,
7118
+ label: "OpenCode model profiles",
7119
+ expected: "complete active model profile"
7120
+ };
7121
+ }
7122
+ function shippedProfilePlanItem() {
7123
+ return {
7124
+ title: "Sync built-in OpenAI model profile (preserve customized roles and valid active selection)",
7125
+ target: targetForProfiles(),
7126
+ preview: JSON.stringify(
7127
+ { version: 1, profiles: { openai: { agents: MODEL_MAPPINGS.openai } } },
7128
+ null,
7129
+ 2
7130
+ )
7107
7131
  };
7108
7132
  }
7109
7133
  function titleCaseSkillName(value) {
@@ -7182,44 +7206,28 @@ function hasManagedPluginDrift(config) {
7182
7206
  isManagedOpenCodePlugin
7183
7207
  ) && !hasExpectedPlugin(config);
7184
7208
  }
7185
- function liteConfigMarker(config) {
7186
- const preset = typeof config?.preset === "string" ? config.preset : void 0;
7187
- const presets = configRecord(config?.presets);
7188
- const managedPreset = preset === OPENAI_PRESET || preset === APPLIED_MODELS_PRESET ? configRecord(presets[preset]) : {};
7189
- const legacyAgents = configRecord(config?.agents);
7190
- const roles = ROLE_NAMES.filter(
7191
- (role) => role in managedPreset || role in legacyAgents
7192
- );
7193
- return `preset: ${preset ?? "none"}; observed roles: ${roles.join(", ")}`;
7209
+ function liteConfigMarker(_config) {
7210
+ const { name, profile } = activeOpenCodeProfile();
7211
+ const roles = ROLE_NAMES.filter((role) => role in (profile?.agents ?? {}));
7212
+ return `profile: ${name ?? "none"}; observed roles: ${roles.join(", ")}`;
7194
7213
  }
7195
- function hasManagedSevenAgentPreset(config) {
7196
- if (!config || config.preset !== OPENAI_PRESET && config.preset !== APPLIED_MODELS_PRESET) {
7197
- return false;
7198
- }
7199
- const presets = configRecord(config.presets);
7200
- return hasCompleteAgentRoster(presets[config.preset]);
7214
+ function hasManagedProfile(_config) {
7215
+ const { name, profile } = activeOpenCodeProfile();
7216
+ return (name === OPENAI_PROFILE || name === APPLIED_MODELS_PROFILE) && hasCompleteAgentRoster(profile?.agents);
7201
7217
  }
7202
- function hasSelectedNamedPreset(config) {
7203
- if (!config || typeof config.preset !== "string" || config.preset.length === 0 || config.preset === OPENAI_PRESET || config.preset === APPLIED_MODELS_PRESET) {
7204
- return false;
7205
- }
7206
- if (!config.presets || typeof config.presets !== "object" || Array.isArray(config.presets)) {
7207
- return false;
7208
- }
7209
- const presets = config.presets;
7210
- if (!Object.hasOwn(presets, config.preset)) return false;
7211
- const selectedPreset = presets[config.preset];
7212
- return selectedPreset !== null && typeof selectedPreset === "object" && !Array.isArray(selectedPreset);
7218
+ function hasSelectedNamedProfile(_config) {
7219
+ const { name, profile } = activeOpenCodeProfile();
7220
+ return !!profile && name !== OPENAI_PROFILE && name !== APPLIED_MODELS_PROFILE;
7213
7221
  }
7214
7222
  function hasLegacySevenAgentRoster(config) {
7215
- return config?.preset === APPLIED_MODELS_PRESET && !hasManagedSevenAgentPreset(config) && hasCompleteAgentRoster(config.agents);
7223
+ return !hasManagedProfile(config) && hasCompleteAgentRoster(config?.agents);
7216
7224
  }
7217
- var ACTIVE_PRESET_SELECTED_CODE = "opencode-active-preset-selected";
7218
- function activePresetSelectedDiagnostic() {
7225
+ var ACTIVE_PROFILE_SELECTED_CODE = "opencode-active-profile-selected";
7226
+ function activeProfileSelectedDiagnostic() {
7219
7227
  return {
7220
7228
  severity: "important",
7221
- message: "A valid named preset is active; model configuration preserves it while sync or install may repair the managed roster.",
7222
- code: ACTIVE_PRESET_SELECTED_CODE
7229
+ message: "A valid named profile is active; model configuration preserves it while sync or install may repair the managed roster.",
7230
+ code: ACTIVE_PROFILE_SELECTED_CODE
7223
7231
  };
7224
7232
  }
7225
7233
  var REPAIRABLE_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
@@ -7227,7 +7235,7 @@ var REPAIRABLE_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
7227
7235
  "opencode-main-config-missing",
7228
7236
  "opencode-lite-config-missing",
7229
7237
  "opencode-roster-drift",
7230
- ACTIVE_PRESET_SELECTED_CODE,
7238
+ ACTIVE_PROFILE_SELECTED_CODE,
7231
7239
  "opencode-required-skills-missing"
7232
7240
  ]);
7233
7241
  var MODEL_PRECONFIG_SKILL_CODES = /* @__PURE__ */ new Set([
@@ -7260,7 +7268,7 @@ function targetsForBlockingDiagnostic(diagnostic, status) {
7260
7268
  if (code === "opencode-plugin-drift" || code === "opencode-main-config-missing" || code === "opencode-config-parse-error" || code === "opencode-unmanaged-config") {
7261
7269
  return main ? [main] : [];
7262
7270
  }
7263
- if (code === "opencode-roster-drift" || code === ACTIVE_PRESET_SELECTED_CODE || code === "opencode-lite-config-missing" || code === "thoth-config-parse-error" || code === "opencode-roster-unrecognized") {
7271
+ if (code === "opencode-roster-drift" || code === ACTIVE_PROFILE_SELECTED_CODE || code === "opencode-lite-config-missing" || code === "thoth-config-parse-error" || code === "opencode-roster-unrecognized") {
7264
7272
  return lite ? [lite] : [];
7265
7273
  }
7266
7274
  if (code === "opencode-required-skills-missing") {
@@ -7288,7 +7296,7 @@ function blockingDetails(action, status, includeRepairable = false) {
7288
7296
  const diagnostics = status.diagnostics.filter((diagnostic) => {
7289
7297
  if (diagnostic.severity === "critical") return true;
7290
7298
  if (diagnostic.severity === "minor") return false;
7291
- if (!includeRepairable && action === "model-config" && diagnostic.code === ACTIVE_PRESET_SELECTED_CODE) {
7299
+ if (!includeRepairable && action === "model-config" && diagnostic.code === ACTIVE_PROFILE_SELECTED_CODE) {
7292
7300
  return false;
7293
7301
  }
7294
7302
  if (!includeRepairable && allowModelPreconfiguration && diagnostic.code !== void 0 && MODEL_PRECONFIG_SKILL_CODES.has(diagnostic.code)) {
@@ -7369,16 +7377,16 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7369
7377
  };
7370
7378
  if (!mainExists || !hasExpectedPlugin(main.config)) {
7371
7379
  if (hasManagedPluginDrift(main.config)) {
7372
- const currentRoster = hasManagedSevenAgentPreset(lite.config);
7380
+ const currentRoster = hasManagedProfile(lite.config);
7373
7381
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
7374
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7375
- const recognizedRoster = currentRoster || legacyRoster || selectedNamedPreset;
7382
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7383
+ const recognizedRoster = currentRoster || legacyRoster || selectedNamedProfile;
7376
7384
  let liteState = "unknown";
7377
7385
  if (!liteExists) {
7378
7386
  liteState = "missing";
7379
7387
  } else if (currentRoster) {
7380
7388
  liteState = "installed";
7381
- } else if (legacyRoster || selectedNamedPreset) {
7389
+ } else if (legacyRoster || selectedNamedProfile) {
7382
7390
  liteState = "drift";
7383
7391
  }
7384
7392
  return {
@@ -7407,7 +7415,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7407
7415
  code: "opencode-roster-drift"
7408
7416
  }
7409
7417
  ] : [],
7410
- ...selectedNamedPreset && !currentRoster ? [activePresetSelectedDiagnostic()] : [],
7418
+ ...selectedNamedProfile && !currentRoster ? [activeProfileSelectedDiagnostic()] : [],
7411
7419
  ...!liteExists ? [
7412
7420
  {
7413
7421
  severity: "important",
@@ -7435,10 +7443,10 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7435
7443
  };
7436
7444
  }
7437
7445
  if (!mainExists && liteExists) {
7438
- const currentRoster = hasManagedSevenAgentPreset(lite.config);
7446
+ const currentRoster = hasManagedProfile(lite.config);
7439
7447
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
7440
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7441
- if (!currentRoster && !legacyRoster && !selectedNamedPreset) {
7448
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7449
+ if (!currentRoster && !legacyRoster && !selectedNamedProfile) {
7442
7450
  return {
7443
7451
  harness: "opencode",
7444
7452
  displayName: "OpenCode",
@@ -7460,12 +7468,12 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7460
7468
  actions: openCodeActions
7461
7469
  };
7462
7470
  }
7463
- if (selectedNamedPreset && !currentRoster) {
7471
+ if (selectedNamedProfile && !currentRoster) {
7464
7472
  return {
7465
7473
  harness: "opencode",
7466
7474
  displayName: "OpenCode",
7467
7475
  state: "drift",
7468
- summary: "OpenCode plugin is missing and a valid named preset is active.",
7476
+ summary: "OpenCode plugin is missing and a valid named profile is active.",
7469
7477
  targets: [
7470
7478
  { ...mainTarget, state: "missing" },
7471
7479
  { ...liteTarget, state: "drift" },
@@ -7477,7 +7485,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7477
7485
  message: "The managed OpenCode main config is missing and must be restored by install or sync.",
7478
7486
  code: "opencode-main-config-missing"
7479
7487
  },
7480
- activePresetSelectedDiagnostic(),
7488
+ activeProfileSelectedDiagnostic(),
7481
7489
  ...skillStatus.diagnostics
7482
7490
  ],
7483
7491
  actions: openCodeActions
@@ -7578,20 +7586,20 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7578
7586
  actions: openCodeActions
7579
7587
  };
7580
7588
  }
7581
- if (!hasManagedSevenAgentPreset(lite.config)) {
7582
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7589
+ if (!hasManagedProfile(lite.config)) {
7590
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7583
7591
  return {
7584
7592
  harness: "opencode",
7585
7593
  displayName: "OpenCode",
7586
7594
  state: "drift",
7587
- summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed six-role roster." : "thoth-agents config does not match the expected six-role roster.",
7595
+ summary: selectedNamedProfile ? "A valid named OpenCode profile is active outside the managed six-role roster." : "thoth-agents config does not match the expected six-role roster.",
7588
7596
  targets: [
7589
7597
  { ...mainTarget, state: "installed" },
7590
7598
  { ...liteTarget, state: "drift" },
7591
7599
  ...skillStatus.targets
7592
7600
  ],
7593
7601
  diagnostics: [
7594
- selectedNamedPreset ? activePresetSelectedDiagnostic() : {
7602
+ selectedNamedProfile ? activeProfileSelectedDiagnostic() : {
7595
7603
  severity: "important",
7596
7604
  message: "Roster drift requires repair before applying generated plans.",
7597
7605
  code: "opencode-roster-drift"
@@ -7625,6 +7633,15 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
7625
7633
  summary: `${managedStatus.summary} Requires OpenCode 2.2.6 or later.`,
7626
7634
  targets: [
7627
7635
  ...managedStatus.targets,
7636
+ {
7637
+ ...targetForProfiles(),
7638
+ observed: JSON.stringify({
7639
+ active: activeOpenCodeProfile().name ?? "default",
7640
+ agents: effectiveOpenCodeProfile(
7641
+ parseConfig(getExistingLiteConfigPath()).config
7642
+ ).agents
7643
+ })
7644
+ },
7628
7645
  getCliManagedInstallVersionTarget("opencode", context2)
7629
7646
  ],
7630
7647
  providerCapability: classifyProviderCapabilityEvidence(evidence)
@@ -7787,6 +7804,7 @@ function buildOpenCodeCompletePlan(action, context2) {
7787
7804
  preview: JSON.stringify(generatedConfig, null, 2),
7788
7805
  backup: defaultBackup(getExistingLiteConfigPath())
7789
7806
  },
7807
+ shippedProfilePlanItem(),
7790
7808
  {
7791
7809
  title: "Inspect package-provided thoth-owned OpenCode skills",
7792
7810
  target: {
@@ -7881,6 +7899,7 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7881
7899
  preview: JSON.stringify(generatedConfig, null, 2),
7882
7900
  backup: defaultBackup(litePath)
7883
7901
  },
7902
+ shippedProfilePlanItem(),
7884
7903
  {
7885
7904
  title: "Inspect package-provided thoth-owned OpenCode skills",
7886
7905
  target: {
@@ -7938,7 +7957,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7938
7957
  const items = normalizedRoles.map(({ role, model, variant, effort }) => {
7939
7958
  return {
7940
7959
  title: `Set ${role} OpenCode model override`,
7941
- target: targetForLiteConfig(),
7960
+ target: targetForProfiles(),
7942
7961
  preview: JSON.stringify({
7943
7962
  [role]: {
7944
7963
  model,
@@ -7961,8 +7980,21 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7961
7980
  "opencode-model-config-preview",
7962
7981
  "model-config",
7963
7982
  "Configure OpenCode role model overrides",
7964
- "Preview thoth-agents role model overrides in the plugin config agents map.",
7965
- items,
7983
+ "Preview complete agents profile materialization and global activation.",
7984
+ [
7985
+ ...items,
7986
+ {
7987
+ title: "Materialize and globally activate the complete agents profile",
7988
+ target: targetForProfiles(),
7989
+ preview: "All six effective roles; active: agents"
7990
+ },
7991
+ {
7992
+ title: "Remove unsupported preset keys, preserving root configuration",
7993
+ target: targetForLiteConfig(),
7994
+ preview: "Retain unrelated config keys; remove preset/presets",
7995
+ backup: defaultBackup(getExistingLiteConfigPath())
7996
+ }
7997
+ ],
7966
7998
  _context
7967
7999
  );
7968
8000
  const effortWarnings = items.flatMap((item) => item.warnings ?? []);
@@ -7987,7 +8019,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7987
8019
  const unsupportedRoles = input.roles.filter(
7988
8020
  (role) => !ROLE_NAMES.includes(role.role)
7989
8021
  );
7990
- if (input.roles.length === 0 || unsupportedRoles.length > 0) {
8022
+ if (unsupportedRoles.length > 0) {
7991
8023
  return {
7992
8024
  ...plan,
7993
8025
  canApply: false,
@@ -7995,7 +8027,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7995
8027
  ...plan.warnings,
7996
8028
  {
7997
8029
  severity: "critical",
7998
- message: input.roles.length === 0 ? "Model plan must include at least one role override." : `Unsupported OpenCode model role(s): ${unsupportedRoles.map((role) => role.role).join(", ")}.`,
8030
+ message: `Unsupported OpenCode model role(s): ${unsupportedRoles.map((role) => role.role).join(", ")}.`,
7999
8031
  code: "opencode-model-roster-incomplete"
8000
8032
  }
8001
8033
  ]
@@ -8119,8 +8151,8 @@ function validateApplyPlan(plan) {
8119
8151
  return null;
8120
8152
  }
8121
8153
  function invalidModelPayloadReason(roles) {
8122
- if (!Array.isArray(roles) || roles.length === 0) {
8123
- return "Model payload must contain at least one role override.";
8154
+ if (!Array.isArray(roles)) {
8155
+ return "Model payload must be a role override array.";
8124
8156
  }
8125
8157
  const seen = /* @__PURE__ */ new Set();
8126
8158
  for (const role of roles) {
@@ -8149,88 +8181,33 @@ function hasCompleteAgentRoster(value) {
8149
8181
  (role) => typeof configRecord(roles[role]).model === "string"
8150
8182
  );
8151
8183
  }
8152
- function effectiveRolePreset(base, generated) {
8153
- const presets = configRecord(base.presets);
8154
- const generatedPresets = configRecord(generated.presets);
8155
- const defaults = configRecord(generatedPresets[OPENAI_PRESET]);
8156
- const selected = typeof base.preset === "string" ? configRecord(presets[base.preset]) : {};
8157
- const selectedIsMaterializedAgentsPreset = base.preset === APPLIED_MODELS_PRESET && hasCompleteAgentRoster(selected);
8158
- const rootAgents = configRecord(base.agents);
8159
- return Object.fromEntries(
8160
- ROLE_NAMES.map((role) => [
8161
- role,
8162
- {
8163
- ...selectedIsMaterializedAgentsPreset ? {} : configRecord(defaults[role]),
8164
- ...configRecord(selected[role]),
8165
- ...configRecord(rootAgents[role])
8166
- }
8167
- ])
8168
- );
8169
- }
8170
8184
  function applyModelPlan(roles) {
8171
- const roleModels = new Map(
8172
- roles.map((role) => [
8173
- role.role,
8174
- { model: role.model, variant: role.variant }
8175
- ])
8176
- );
8177
8185
  ensureConfigDir();
8178
8186
  const targetPath = getExistingLiteConfigPath();
8179
8187
  const parsed = parseConfig(targetPath);
8180
- const generated = generateLiteConfig({
8181
- agent: "opencode",
8182
- dryRun: false,
8183
- reset: false
8184
- });
8185
- const base = parsed.config ?? generated;
8186
- const agents = { ...configRecord(base.agents) };
8187
- const appliedPreset = effectiveRolePreset(base, generated);
8188
+ if (parsed.error) throw new Error(parsed.error);
8189
+ const base = parsed.config ?? generateLiteConfig({ agent: "opencode", reset: false });
8190
+ const profile = effectiveOpenCodeProfile(base);
8188
8191
  const statePath = getOpenCodeManagedModelStatePath();
8189
8192
  const state = readManagedModelState(statePath, 1);
8190
8193
  const configuredEfforts = { ...state.configuredEfforts ?? {} };
8191
8194
  const warnings = [];
8192
- for (const [role, override] of roleModels) {
8193
- const current = {
8194
- ...configRecord(agents[role])
8195
+ for (const override of roles) {
8196
+ const role = override.role;
8197
+ profile.agents[role] = {
8198
+ model: override.model,
8199
+ ...override.variant !== null ? { variant: override.variant } : {}
8195
8200
  };
8196
- const presetRole = {
8197
- ...configRecord(appliedPreset[role])
8198
- };
8199
- const currentVariant = typeof current.variant === "string" ? current.variant : void 0;
8200
- const trackedVariant = configuredEfforts[role];
8201
- const userOwnsVariant = override.variant === null && currentVariant !== void 0 && (trackedVariant === void 0 || currentVariant !== trackedVariant);
8202
- current.model = override.model;
8203
- presetRole.model = override.model;
8204
- if (userOwnsVariant) {
8205
- presetRole.variant = currentVariant;
8206
- delete configuredEfforts[role];
8207
- warnings.push({
8208
- severity: "important",
8209
- code: "opencode-effort-user-owned",
8210
- message: `Preserved user-owned OpenCode variant ${currentVariant} for ${role}.`
8211
- });
8212
- } else if (override.variant === null) {
8213
- delete current.variant;
8214
- delete presetRole.variant;
8215
- delete configuredEfforts[role];
8216
- } else {
8217
- current.variant = override.variant;
8218
- presetRole.variant = override.variant;
8219
- configuredEfforts[role] = override.variant;
8220
- }
8221
- agents[role] = current;
8222
- appliedPreset[role] = presetRole;
8201
+ if (override.variant === null) delete configuredEfforts[role];
8202
+ else configuredEfforts[role] = override.variant;
8223
8203
  }
8224
- base.agents = agents;
8225
- base.presets = {
8226
- ...configRecord(base.presets),
8227
- [APPLIED_MODELS_PRESET]: appliedPreset
8228
- };
8229
- base.preset = APPLIED_MODELS_PRESET;
8230
- writeConfig(targetPath, base);
8204
+ const store = openCodeProfileStore();
8205
+ store.save("global", APPLIED_MODELS_PROFILE, profile);
8206
+ store.setGlobalActive(APPLIED_MODELS_PROFILE);
8207
+ writeConfig(targetPath, withoutLegacyPresets(base));
8231
8208
  writeManagedModelState(statePath, 1, {
8232
8209
  models: state.models,
8233
- configuredEfforts: Object.keys(configuredEfforts).length > 0 ? configuredEfforts : void 0
8210
+ configuredEfforts: Object.keys(configuredEfforts).length ? configuredEfforts : void 0
8234
8211
  });
8235
8212
  return {
8236
8213
  harness: "opencode",
@@ -8240,7 +8217,11 @@ function applyModelPlan(roles) {
8240
8217
  changedTargets: [
8241
8218
  {
8242
8219
  ...targetForLiteConfig("installed"),
8243
- observed: "agents role overrides updated"
8220
+ observed: "root configuration preserved; unsupported preset keys removed"
8221
+ },
8222
+ {
8223
+ ...targetForProfiles(),
8224
+ observed: "complete agents profile activated"
8244
8225
  }
8245
8226
  ],
8246
8227
  backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
@@ -8365,7 +8346,17 @@ function applyOpenCodePlan(plan) {
8365
8346
  issued.context
8366
8347
  );
8367
8348
  }
8368
- return applyModelPlan(issued.payload.roles);
8349
+ try {
8350
+ return applyModelPlan(issued.payload.roles);
8351
+ } catch (error) {
8352
+ return rejectPlan3(
8353
+ plan,
8354
+ `Failed to apply OpenCode model profile: ${String(error)}`,
8355
+ "critical",
8356
+ "opencode-profile-write-failed",
8357
+ issued.context
8358
+ );
8359
+ }
8369
8360
  }
8370
8361
  const changedTargets = [];
8371
8362
  const backups = [];
@@ -8413,6 +8404,10 @@ function applyOpenCodePlan(plan) {
8413
8404
  ...targetForLiteConfig("installed"),
8414
8405
  observed: "six-role roster written"
8415
8406
  });
8407
+ changedTargets.push({
8408
+ ...targetForProfiles(),
8409
+ observed: "built-in openai profile synchronized"
8410
+ });
8416
8411
  if (existsSync10(`${liteResult.configPath}.bak`)) {
8417
8412
  backups.push({
8418
8413
  path: `${liteResult.configPath}.bak`,
@@ -12183,35 +12178,15 @@ function getClaudeCodeModelRoles(_source = claudeCodeContext) {
12183
12178
  function getPiModelRoles() {
12184
12179
  return defaultPiModelRoles(context);
12185
12180
  }
12186
- function readRoleField(config, role, field) {
12187
- if (!config || typeof config !== "object" || Array.isArray(config)) {
12188
- return void 0;
12189
- }
12190
- const record = config;
12191
- const value = record[role];
12192
- if (!value || typeof value !== "object" || Array.isArray(value)) {
12193
- return void 0;
12194
- }
12195
- const fieldValue = value[field];
12196
- return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : void 0;
12197
- }
12198
12181
  function getOpenCodeModelRoles() {
12199
12182
  const parsed = parseConfig(getExistingLiteConfigPath());
12200
- const agents = parsed.config?.agents && typeof parsed.config.agents === "object" ? parsed.config.agents : void 0;
12201
- const presets = parsed.config?.presets && typeof parsed.config.presets === "object" && !Array.isArray(parsed.config.presets) ? parsed.config.presets : {};
12202
- const presetName = typeof parsed.config?.preset === "string" ? parsed.config.preset : void 0;
12203
- const selectedPreset = presetName !== void 0 ? presets[presetName] : void 0;
12204
- const activePreset = selectedPreset && typeof selectedPreset === "object" && !Array.isArray(selectedPreset) ? selectedPreset : void 0;
12183
+ const profile = effectiveOpenCodeProfile(parsed.config);
12205
12184
  return ALL_AGENT_NAMES.map((role) => {
12206
- const defaultModel = getDefaultOpenCodeModel(role);
12207
- const configuredModel = readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model");
12208
- const model = configuredModel ?? defaultModel;
12209
- const configuredVariant = readRoleField(agents, role, "variant") ?? readRoleField(activePreset, role, "variant");
12210
- const variant = configuredVariant ?? (configuredModel === void 0 ? getDefaultOpenCodeVariant(role) : void 0);
12185
+ const value = profile.agents[role];
12211
12186
  return {
12212
12187
  role,
12213
- model,
12214
- effort: variant ? { kind: "effort", value: variant } : { kind: "inherit" }
12188
+ model: value?.model ?? getDefaultOpenCodeModel(role),
12189
+ effort: value?.variant ? { kind: "effort", value: value.variant } : { kind: "inherit" }
12215
12190
  };
12216
12191
  });
12217
12192
  }