thoth-agents 0.3.5 → 0.3.6

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.
@@ -3777,8 +3777,8 @@ function applyCodexPlan(plan) {
3777
3777
 
3778
3778
  // src/cli/operations/opencode.ts
3779
3779
  import { createHash as createHash3 } from "crypto";
3780
- import { existsSync as existsSync9 } from "fs";
3781
- import { join as join4 } from "path";
3780
+ import { existsSync as existsSync10 } from "fs";
3781
+ import { join as join5 } from "path";
3782
3782
 
3783
3783
  // src/cli/opencode-effort.ts
3784
3784
  var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
@@ -3810,10 +3810,127 @@ function resolveOpenCodeEffort(input) {
3810
3810
  };
3811
3811
  }
3812
3812
 
3813
+ // src/cli/owned-skills.ts
3814
+ import {
3815
+ cpSync,
3816
+ existsSync as existsSync9,
3817
+ mkdirSync as mkdirSync3,
3818
+ mkdtempSync,
3819
+ renameSync as renameSync2,
3820
+ rmSync,
3821
+ statSync
3822
+ } from "fs";
3823
+ import { homedir as homedir3 } from "os";
3824
+ import { dirname as dirname5, join as join4 } from "path";
3825
+ import { fileURLToPath as fileURLToPath4 } from "url";
3826
+
3827
+ // src/harness/core/owned-skills.ts
3828
+ var THOTH_OWNED_SKILL_NAMES = [
3829
+ "thoth-init",
3830
+ "thoth-sdd",
3831
+ "thoth-constitution",
3832
+ "thoth-archive",
3833
+ "plan-reviewer"
3834
+ ];
3835
+
3836
+ // src/cli/owned-skills.ts
3837
+ function resolveOwnedSkillPackageRoot(moduleUrl) {
3838
+ const packageRoot = findPackageRoot(dirname5(fileURLToPath4(moduleUrl)));
3839
+ if (!packageRoot) {
3840
+ throw new Error(
3841
+ "Unable to locate the thoth-agents package root for bundled owned skills."
3842
+ );
3843
+ }
3844
+ return packageRoot;
3845
+ }
3846
+ function defaultPackageRoot() {
3847
+ return resolveOwnedSkillPackageRoot(import.meta.url);
3848
+ }
3849
+ function getOpenCodeOwnedSkillEntries(options = {}) {
3850
+ const packageRoot = options.packageRoot ?? defaultPackageRoot();
3851
+ const homeDir = options.homeDir ?? homedir3();
3852
+ return THOTH_OWNED_SKILL_NAMES.map((name) => ({
3853
+ name,
3854
+ sourcePath: join4(packageRoot, "skills", name),
3855
+ destinationPath: join4(homeDir, ".config", "opencode", "skills", name)
3856
+ }));
3857
+ }
3858
+ function validateCanonicalSkill(entry) {
3859
+ if (!existsSync9(entry.sourcePath) || !statSync(entry.sourcePath).isDirectory()) {
3860
+ throw new Error(
3861
+ `Canonical owned skill directory is missing: ${entry.name}`
3862
+ );
3863
+ }
3864
+ const contractPath = join4(entry.sourcePath, "SKILL.md");
3865
+ if (!existsSync9(contractPath) || !statSync(contractPath).isFile()) {
3866
+ throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
3867
+ }
3868
+ }
3869
+ function installOwnedSkills(skills) {
3870
+ const firstSkill = skills[0];
3871
+ if (!firstSkill) return;
3872
+ const globalRoot = dirname5(firstSkill.destinationPath);
3873
+ mkdirSync3(globalRoot, { recursive: true });
3874
+ const transactionRoot = mkdtempSync(join4(globalRoot, ".thoth-agents-owned-"));
3875
+ const stagedRoot = join4(transactionRoot, "staged");
3876
+ const backupRoot = join4(transactionRoot, "backup");
3877
+ mkdirSync3(stagedRoot, { recursive: true });
3878
+ mkdirSync3(backupRoot, { recursive: true });
3879
+ try {
3880
+ for (const skill of skills) {
3881
+ cpSync(skill.sourcePath, join4(stagedRoot, skill.name), {
3882
+ recursive: true,
3883
+ force: true
3884
+ });
3885
+ }
3886
+ for (const skill of skills) {
3887
+ const stagedPath = join4(stagedRoot, skill.name);
3888
+ const backupPath = join4(backupRoot, skill.name);
3889
+ const hadExistingDestination = existsSync9(skill.destinationPath);
3890
+ if (hadExistingDestination) {
3891
+ renameSync2(skill.destinationPath, backupPath);
3892
+ }
3893
+ try {
3894
+ renameSync2(stagedPath, skill.destinationPath);
3895
+ } catch (error) {
3896
+ if (existsSync9(skill.destinationPath)) {
3897
+ rmSync(skill.destinationPath, { recursive: true, force: true });
3898
+ }
3899
+ if (hadExistingDestination && existsSync9(backupPath)) {
3900
+ renameSync2(backupPath, skill.destinationPath);
3901
+ }
3902
+ throw error;
3903
+ }
3904
+ if (existsSync9(backupPath)) {
3905
+ rmSync(backupPath, { recursive: true, force: true });
3906
+ }
3907
+ }
3908
+ } finally {
3909
+ rmSync(transactionRoot, { recursive: true, force: true });
3910
+ }
3911
+ }
3912
+ function syncOpenCodeOwnedSkills(options = {}) {
3913
+ const skills = getOpenCodeOwnedSkillEntries(options);
3914
+ try {
3915
+ for (const skill of skills) validateCanonicalSkill(skill);
3916
+ if (options.dryRun) return { success: true, status: "planned", skills };
3917
+ installOwnedSkills(skills);
3918
+ return { success: true, status: "installed", skills };
3919
+ } catch (error) {
3920
+ return {
3921
+ success: false,
3922
+ status: "failed",
3923
+ skills,
3924
+ error: error instanceof Error ? error.message : String(error)
3925
+ };
3926
+ }
3927
+ }
3928
+
3813
3929
  // src/cli/operations/opencode.ts
3814
3930
  var PACKAGE_NAME = "thoth-agents";
3815
3931
  var EXPECTED_PLUGIN = `${PACKAGE_NAME}@latest`;
3816
3932
  var OPENAI_PRESET = "openai";
3933
+ var APPLIED_MODELS_PRESET = "agents";
3817
3934
  var ROLE_NAMES = [...ALL_AGENT_NAMES];
3818
3935
  var issuedOpenCodePlans = /* @__PURE__ */ new WeakMap();
3819
3936
  var NonCanonicalDataError = class extends Error {
@@ -4044,7 +4161,7 @@ function titleCaseSkillName(value) {
4044
4161
  }).join("-");
4045
4162
  }
4046
4163
  function homeDirFromContext(context2) {
4047
- return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join4(context2.cwd, "home") : void 0);
4164
+ return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join5(context2.cwd, "home") : void 0);
4048
4165
  }
4049
4166
  function openCodeSkillTargets(context2) {
4050
4167
  const homeDir = homeDirFromContext(context2);
@@ -4065,8 +4182,24 @@ function openCodeSkillTargets(context2) {
4065
4182
  observed: installed ? "required global skill installed" : "required global skill missing"
4066
4183
  };
4067
4184
  });
4185
+ const ownedTargets = getOpenCodeOwnedSkillEntries({
4186
+ homeDir
4187
+ }).map((skill) => {
4188
+ const path = join5(skill.destinationPath, "SKILL.md");
4189
+ const installed = existsSync10(path);
4190
+ return {
4191
+ kind: "skill",
4192
+ path,
4193
+ label: titleCaseSkillName(skill.name),
4194
+ state: installed ? "installed" : "missing",
4195
+ expected: "required global OpenCode skill",
4196
+ observed: installed ? "thoth-owned global skill installed" : "thoth-owned global skill missing"
4197
+ };
4198
+ });
4068
4199
  const diagnostics = [];
4069
- if (requiredTargets.some((target) => target.state === "missing")) {
4200
+ if ([...requiredTargets, ...ownedTargets].some(
4201
+ (target) => target.state === "missing"
4202
+ )) {
4070
4203
  diagnostics.push({
4071
4204
  severity: "important",
4072
4205
  message: "Required OpenCode global skills are missing; run install or sync to restore them.",
@@ -4074,7 +4207,7 @@ function openCodeSkillTargets(context2) {
4074
4207
  });
4075
4208
  }
4076
4209
  return {
4077
- targets: requiredTargets,
4210
+ targets: [...requiredTargets, ...ownedTargets],
4078
4211
  diagnostics
4079
4212
  };
4080
4213
  }
@@ -4092,28 +4225,23 @@ function hasManagedPluginDrift(config) {
4092
4225
  }
4093
4226
  function liteConfigMarker(config) {
4094
4227
  const preset = typeof config?.preset === "string" ? config.preset : void 0;
4095
- const presets = config?.presets && typeof config.presets === "object" ? config.presets : {};
4096
- const openaiPreset = presets[OPENAI_PRESET] && typeof presets[OPENAI_PRESET] === "object" ? presets[OPENAI_PRESET] : {};
4097
- const legacyAgents = config?.agents && typeof config.agents === "object" ? config.agents : {};
4228
+ const presets = configRecord(config?.presets);
4229
+ const managedPreset = preset === OPENAI_PRESET || preset === APPLIED_MODELS_PRESET ? configRecord(presets[preset]) : {};
4230
+ const legacyAgents = configRecord(config?.agents);
4098
4231
  const roles = ROLE_NAMES.filter(
4099
- (role) => role in openaiPreset || role in legacyAgents
4232
+ (role) => role in managedPreset || role in legacyAgents
4100
4233
  );
4101
4234
  return `preset: ${preset ?? "none"}; observed roles: ${roles.join(", ")}`;
4102
4235
  }
4103
- function hasSevenAgentPreset(config) {
4104
- if (!config || config.preset !== OPENAI_PRESET) return false;
4105
- if (!config.presets || typeof config.presets !== "object") return false;
4106
- const presets = config.presets;
4107
- const openaiPreset = presets[OPENAI_PRESET];
4108
- if (!openaiPreset || typeof openaiPreset !== "object") return false;
4109
- const roles = openaiPreset;
4110
- return ROLE_NAMES.every((role) => {
4111
- const value = roles[role];
4112
- return value !== null && typeof value === "object" && typeof value.model === "string";
4113
- });
4236
+ function hasManagedSevenAgentPreset(config) {
4237
+ if (!config || config.preset !== OPENAI_PRESET && config.preset !== APPLIED_MODELS_PRESET) {
4238
+ return false;
4239
+ }
4240
+ const presets = configRecord(config.presets);
4241
+ return hasCompleteAgentRoster(presets[config.preset]);
4114
4242
  }
4115
4243
  function hasSelectedNamedPreset(config) {
4116
- if (!config || typeof config.preset !== "string" || config.preset.length === 0 || config.preset === OPENAI_PRESET || config.preset === "agents") {
4244
+ if (!config || typeof config.preset !== "string" || config.preset.length === 0 || config.preset === OPENAI_PRESET || config.preset === APPLIED_MODELS_PRESET) {
4117
4245
  return false;
4118
4246
  }
4119
4247
  if (!config.presets || typeof config.presets !== "object" || Array.isArray(config.presets)) {
@@ -4125,13 +4253,7 @@ function hasSelectedNamedPreset(config) {
4125
4253
  return selectedPreset !== null && typeof selectedPreset === "object" && !Array.isArray(selectedPreset);
4126
4254
  }
4127
4255
  function hasLegacySevenAgentRoster(config) {
4128
- if (!config || config.preset !== "agents") return false;
4129
- if (!config.agents || typeof config.agents !== "object") return false;
4130
- const agents = config.agents;
4131
- return ROLE_NAMES.every((role) => {
4132
- const value = agents[role];
4133
- return value !== null && typeof value === "object" && typeof value.model === "string";
4134
- });
4256
+ return config?.preset === APPLIED_MODELS_PRESET && !hasManagedSevenAgentPreset(config) && hasCompleteAgentRoster(config.agents);
4135
4257
  }
4136
4258
  var ACTIVE_PRESET_SELECTED_CODE = "opencode-active-preset-selected";
4137
4259
  function activePresetSelectedDiagnostic() {
@@ -4184,7 +4306,7 @@ function targetsForBlockingDiagnostic(diagnostic, status) {
4184
4306
  }
4185
4307
  if (code === "opencode-required-skills-missing") {
4186
4308
  return status.targets.filter(
4187
- (target) => target.expected === "required global OpenCode skill" && target.state !== "installed"
4309
+ (target) => target.kind === "skill" && target.state !== "installed"
4188
4310
  );
4189
4311
  }
4190
4312
  return [
@@ -4269,8 +4391,8 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4269
4391
  actions: openCodeActions
4270
4392
  };
4271
4393
  }
4272
- const mainExists = existsSync9(mainPath);
4273
- const liteExists = existsSync9(litePath);
4394
+ const mainExists = existsSync10(mainPath);
4395
+ const liteExists = existsSync10(litePath);
4274
4396
  const mainTarget = {
4275
4397
  ...targetForMainConfig(),
4276
4398
  observed: configPluginMarker(main.config)
@@ -4281,7 +4403,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4281
4403
  };
4282
4404
  if (!mainExists || !hasExpectedPlugin(main.config)) {
4283
4405
  if (hasManagedPluginDrift(main.config)) {
4284
- const currentRoster = hasSevenAgentPreset(lite.config);
4406
+ const currentRoster = hasManagedSevenAgentPreset(lite.config);
4285
4407
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
4286
4408
  const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
4287
4409
  const recognizedRoster = currentRoster || legacyRoster || selectedNamedPreset;
@@ -4347,7 +4469,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4347
4469
  };
4348
4470
  }
4349
4471
  if (!mainExists && liteExists) {
4350
- const currentRoster = hasSevenAgentPreset(lite.config);
4472
+ const currentRoster = hasManagedSevenAgentPreset(lite.config);
4351
4473
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
4352
4474
  const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
4353
4475
  if (!currentRoster && !legacyRoster && !selectedNamedPreset) {
@@ -4490,7 +4612,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4490
4612
  actions: openCodeActions
4491
4613
  };
4492
4614
  }
4493
- if (!hasSevenAgentPreset(lite.config)) {
4615
+ if (!hasManagedSevenAgentPreset(lite.config)) {
4494
4616
  const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
4495
4617
  return {
4496
4618
  harness: "opencode",
@@ -4520,7 +4642,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4520
4642
  harness: "opencode",
4521
4643
  displayName: "OpenCode",
4522
4644
  state: requiredSkillsMissing ? "drift" : "installed",
4523
- summary: requiredSkillsMissing ? "OpenCode configuration is installed, but required external skills are missing." : "OpenCode managed thoth-agents configuration is installed.",
4645
+ summary: requiredSkillsMissing ? "OpenCode configuration is installed, but required global skills are missing." : "OpenCode managed thoth-agents configuration is installed.",
4524
4646
  targets: [
4525
4647
  { ...mainTarget, state: "installed" },
4526
4648
  { ...liteTarget, state: "installed" },
@@ -4638,7 +4760,7 @@ function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
4638
4760
  action: "update"
4639
4761
  });
4640
4762
  }
4641
- function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
4763
+ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
4642
4764
  const generatedConfig = generateLiteConfig({
4643
4765
  agent: "opencode",
4644
4766
  hasTmux: false,
@@ -4646,6 +4768,10 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
4646
4768
  reset: false
4647
4769
  });
4648
4770
  const litePath = getExistingLiteConfigPath();
4771
+ const ownedSkills = syncOpenCodeOwnedSkills({
4772
+ dryRun: true,
4773
+ homeDir: homeDirFromContext(context2)
4774
+ });
4649
4775
  const { plan, status } = planFromItems(
4650
4776
  "opencode-sync-preview",
4651
4777
  "sync",
@@ -4655,7 +4781,7 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
4655
4781
  {
4656
4782
  title: "Ensure OpenCode plugin points at thoth-agents@latest",
4657
4783
  target: targetForMainConfig(),
4658
- state: getOpenCodeStatus(_context).state,
4784
+ state: getOpenCodeStatus(context2).state,
4659
4785
  preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
4660
4786
  backup: defaultBackup(getExistingConfigPath())
4661
4787
  },
@@ -4671,6 +4797,15 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
4671
4797
  preview: JSON.stringify(generatedConfig, null, 2),
4672
4798
  backup: defaultBackup(litePath)
4673
4799
  },
4800
+ {
4801
+ title: "Synchronize global thoth-owned OpenCode skills",
4802
+ target: {
4803
+ kind: "skill",
4804
+ label: "Thoth-owned OpenCode skills",
4805
+ expected: ownedSkills.skills.map(({ name }) => name).join(", ")
4806
+ },
4807
+ preview: JSON.stringify(ownedSkills, null, 2)
4808
+ },
4674
4809
  {
4675
4810
  title: "Install required external skills",
4676
4811
  target: {
@@ -4688,9 +4823,9 @@ function buildOpenCodeSyncPlan(_context = { cwd: process.cwd() }) {
4688
4823
  )
4689
4824
  }
4690
4825
  ],
4691
- _context
4826
+ context2
4692
4827
  );
4693
- return issueOpenCodePlan(plan, status, _context, {
4828
+ return issueOpenCodePlan(plan, status, context2, {
4694
4829
  kind: "fixed",
4695
4830
  action: "sync"
4696
4831
  });
@@ -4703,11 +4838,16 @@ function tuiInstallConfig() {
4703
4838
  reset: false
4704
4839
  };
4705
4840
  }
4706
- function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
4841
+ function buildOpenCodeInstallPlan(context2 = { cwd: process.cwd() }) {
4842
+ const ownedSkills = syncOpenCodeOwnedSkills({
4843
+ dryRun: true,
4844
+ homeDir: homeDirFromContext(context2)
4845
+ });
4707
4846
  const generatedConfig = generateLiteConfig(tuiInstallConfig());
4708
4847
  const installPreview = {
4709
4848
  noTui: true,
4710
4849
  hasTmux: false,
4850
+ ownedSkills: ownedSkills.skills.map((skill) => skill.name),
4711
4851
  requiredSkills: REQUIRED_SKILLS.map((skill) => skill.name),
4712
4852
  equivalentCommand: "install --agent=opencode --no-tui --tmux=no"
4713
4853
  };
@@ -4716,7 +4856,7 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
4716
4856
  "opencode-install-preview",
4717
4857
  "install",
4718
4858
  "Preview install",
4719
- "Preview OpenCode install with the required external skills.",
4859
+ "Preview OpenCode install with the required global skills.",
4720
4860
  [
4721
4861
  {
4722
4862
  title: "Apply OpenCode TUI install options",
@@ -4724,15 +4864,15 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
4724
4864
  kind: "config",
4725
4865
  path: getExistingConfigPath(),
4726
4866
  label: "OpenCode install options",
4727
- state: getOpenCodeStatus(_context).state,
4728
- expected: "--no-tui --tmux=no plus required external skills"
4867
+ state: getOpenCodeStatus(context2).state,
4868
+ expected: "--no-tui --tmux=no plus required global skills"
4729
4869
  },
4730
4870
  preview: JSON.stringify(installPreview, null, 2)
4731
4871
  },
4732
4872
  {
4733
4873
  title: "Ensure OpenCode plugin points at thoth-agents@latest",
4734
4874
  target: targetForMainConfig(),
4735
- state: getOpenCodeStatus(_context).state,
4875
+ state: getOpenCodeStatus(context2).state,
4736
4876
  preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
4737
4877
  backup: defaultBackup(getExistingConfigPath())
4738
4878
  },
@@ -4748,6 +4888,15 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
4748
4888
  preview: JSON.stringify(generatedConfig, null, 2),
4749
4889
  backup: defaultBackup(litePath)
4750
4890
  },
4891
+ {
4892
+ title: "Synchronize global thoth-owned OpenCode skills",
4893
+ target: {
4894
+ kind: "skill",
4895
+ label: "Thoth-owned OpenCode skills",
4896
+ expected: ownedSkills.skills.map(({ name }) => name).join(", ")
4897
+ },
4898
+ preview: JSON.stringify(ownedSkills, null, 2)
4899
+ },
4751
4900
  {
4752
4901
  title: "Install required external skills",
4753
4902
  target: {
@@ -4765,9 +4914,9 @@ function buildOpenCodeInstallPlan(_context = { cwd: process.cwd() }) {
4765
4914
  )
4766
4915
  }
4767
4916
  ],
4768
- _context
4917
+ context2
4769
4918
  );
4770
- return issueOpenCodePlan(plan, status, _context, {
4919
+ return issueOpenCodePlan(plan, status, context2, {
4771
4920
  kind: "fixed",
4772
4921
  action: "install"
4773
4922
  });
@@ -4996,6 +5145,33 @@ function invalidModelPayloadReason(roles) {
4996
5145
  }
4997
5146
  return void 0;
4998
5147
  }
5148
+ function configRecord(value) {
5149
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
5150
+ }
5151
+ function hasCompleteAgentRoster(value) {
5152
+ const roles = configRecord(value);
5153
+ return ROLE_NAMES.every(
5154
+ (role) => typeof configRecord(roles[role]).model === "string"
5155
+ );
5156
+ }
5157
+ function effectiveRolePreset(base, generated) {
5158
+ const presets = configRecord(base.presets);
5159
+ const generatedPresets = configRecord(generated.presets);
5160
+ const defaults = configRecord(generatedPresets[OPENAI_PRESET]);
5161
+ const selected = typeof base.preset === "string" ? configRecord(presets[base.preset]) : {};
5162
+ const selectedIsMaterializedAgentsPreset = base.preset === APPLIED_MODELS_PRESET && hasCompleteAgentRoster(selected);
5163
+ const rootAgents = configRecord(base.agents);
5164
+ return Object.fromEntries(
5165
+ ROLE_NAMES.map((role) => [
5166
+ role,
5167
+ {
5168
+ ...selectedIsMaterializedAgentsPreset ? {} : configRecord(defaults[role]),
5169
+ ...configRecord(selected[role]),
5170
+ ...configRecord(rootAgents[role])
5171
+ }
5172
+ ])
5173
+ );
5174
+ }
4999
5175
  function applyModelPlan(roles) {
5000
5176
  const roleModels = new Map(
5001
5177
  roles.map((role) => [
@@ -5006,26 +5182,33 @@ function applyModelPlan(roles) {
5006
5182
  ensureConfigDir();
5007
5183
  const targetPath = getExistingLiteConfigPath();
5008
5184
  const parsed = parseConfig(targetPath);
5009
- const base = parsed.config ?? generateLiteConfig({
5185
+ const generated = generateLiteConfig({
5010
5186
  agent: "opencode",
5011
5187
  hasTmux: false,
5012
5188
  dryRun: false,
5013
5189
  reset: false
5014
5190
  });
5015
- const agents = base.agents && typeof base.agents === "object" ? { ...base.agents } : {};
5191
+ const base = parsed.config ?? generated;
5192
+ const agents = { ...configRecord(base.agents) };
5193
+ const appliedPreset = effectiveRolePreset(base, generated);
5016
5194
  const statePath = getOpenCodeManagedModelStatePath();
5017
5195
  const state = readManagedModelState(statePath, 1);
5018
5196
  const configuredEfforts = { ...state.configuredEfforts ?? {} };
5019
5197
  const warnings = [];
5020
5198
  for (const [role, override] of roleModels) {
5021
5199
  const current = {
5022
- ...agents[role] ?? {}
5200
+ ...configRecord(agents[role])
5201
+ };
5202
+ const presetRole = {
5203
+ ...configRecord(appliedPreset[role])
5023
5204
  };
5024
5205
  const currentVariant = typeof current.variant === "string" ? current.variant : void 0;
5025
5206
  const trackedVariant = configuredEfforts[role];
5026
5207
  const userOwnsVariant = override.variant === null && currentVariant !== void 0 && (trackedVariant === void 0 || currentVariant !== trackedVariant);
5027
5208
  current.model = override.model;
5209
+ presetRole.model = override.model;
5028
5210
  if (userOwnsVariant) {
5211
+ presetRole.variant = currentVariant;
5029
5212
  delete configuredEfforts[role];
5030
5213
  warnings.push({
5031
5214
  severity: "important",
@@ -5034,14 +5217,22 @@ function applyModelPlan(roles) {
5034
5217
  });
5035
5218
  } else if (override.variant === null) {
5036
5219
  delete current.variant;
5220
+ delete presetRole.variant;
5037
5221
  delete configuredEfforts[role];
5038
5222
  } else {
5039
5223
  current.variant = override.variant;
5224
+ presetRole.variant = override.variant;
5040
5225
  configuredEfforts[role] = override.variant;
5041
5226
  }
5042
5227
  agents[role] = current;
5228
+ appliedPreset[role] = presetRole;
5043
5229
  }
5044
5230
  base.agents = agents;
5231
+ base.presets = {
5232
+ ...configRecord(base.presets),
5233
+ [APPLIED_MODELS_PRESET]: appliedPreset
5234
+ };
5235
+ base.preset = APPLIED_MODELS_PRESET;
5045
5236
  writeConfig(targetPath, base);
5046
5237
  writeTextWithBackup(
5047
5238
  statePath,
@@ -5062,7 +5253,7 @@ function applyModelPlan(roles) {
5062
5253
  observed: "agents role overrides updated"
5063
5254
  }
5064
5255
  ],
5065
- backups: existsSync9(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
5256
+ backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
5066
5257
  warnings,
5067
5258
  disclaimers: defaultDisclaimers()
5068
5259
  };
@@ -5090,6 +5281,22 @@ function applyRequiredSkills(context2) {
5090
5281
  }
5091
5282
  return { success: warnings.length === 0, warnings };
5092
5283
  }
5284
+ function applyOwnedSkills(context2) {
5285
+ const result = syncOpenCodeOwnedSkills({
5286
+ homeDir: homeDirFromContext(context2)
5287
+ });
5288
+ if (result.success) return { success: true, warnings: [] };
5289
+ return {
5290
+ success: false,
5291
+ warnings: [
5292
+ {
5293
+ severity: "critical",
5294
+ code: "opencode-owned-skills-sync-failed",
5295
+ message: `Failed to synchronize thoth-owned OpenCode skills: ${result.error ?? "unknown error"}.`
5296
+ }
5297
+ ]
5298
+ };
5299
+ }
5093
5300
  function applyOpenCodePlan(plan) {
5094
5301
  const rejection = validateApplyPlan(plan);
5095
5302
  if (rejection) return rejection;
@@ -5145,7 +5352,7 @@ function applyOpenCodePlan(plan) {
5145
5352
  ...targetForMainConfig("installed"),
5146
5353
  observed: `plugin includes ${EXPECTED_PLUGIN}`
5147
5354
  });
5148
- if (existsSync9(`${mainConfigResult.configPath}.bak`)) {
5355
+ if (existsSync10(`${mainConfigResult.configPath}.bak`)) {
5149
5356
  backups.push({
5150
5357
  path: `${mainConfigResult.configPath}.bak`,
5151
5358
  label: "OpenCode config backup"
@@ -5171,7 +5378,7 @@ function applyOpenCodePlan(plan) {
5171
5378
  ...targetForLiteConfig("installed"),
5172
5379
  observed: "seven-role roster written"
5173
5380
  });
5174
- if (existsSync9(`${liteResult.configPath}.bak`)) {
5381
+ if (existsSync10(`${liteResult.configPath}.bak`)) {
5175
5382
  backups.push({
5176
5383
  path: `${liteResult.configPath}.bak`,
5177
5384
  label: "thoth-agents config backup"
@@ -5179,6 +5386,26 @@ function applyOpenCodePlan(plan) {
5179
5386
  }
5180
5387
  }
5181
5388
  if (issued.payload.action === "install" || issued.payload.action === "sync") {
5389
+ const ownedSkills = applyOwnedSkills(issued.context);
5390
+ warnings.push(...ownedSkills.warnings);
5391
+ changedTargets.push({
5392
+ kind: "skill",
5393
+ label: "Thoth-owned OpenCode skills",
5394
+ state: ownedSkills.success ? "installed" : "drift",
5395
+ observed: ownedSkills.success ? "thoth-owned global skills synchronized" : "thoth-owned global skill synchronization failed"
5396
+ });
5397
+ if (!ownedSkills.success) {
5398
+ return {
5399
+ harness: "opencode",
5400
+ action: plan.action,
5401
+ applied: false,
5402
+ summary: "OpenCode configuration was written, but thoth-owned skills failed to synchronize.",
5403
+ changedTargets,
5404
+ backups,
5405
+ warnings,
5406
+ disclaimers: defaultDisclaimers()
5407
+ };
5408
+ }
5182
5409
  const requiredSkills = applyRequiredSkills(issued.context);
5183
5410
  warnings.push(...requiredSkills.warnings);
5184
5411
  changedTargets.push({
@@ -5235,15 +5462,15 @@ import { execFileSync } from "child_process";
5235
5462
 
5236
5463
  // src/cli/model-catalog/cache.ts
5237
5464
  import {
5238
- existsSync as existsSync10,
5239
- mkdirSync as mkdirSync3,
5465
+ existsSync as existsSync11,
5466
+ mkdirSync as mkdirSync4,
5240
5467
  readFileSync as readFileSync6,
5241
- renameSync as renameSync2,
5468
+ renameSync as renameSync3,
5242
5469
  unlinkSync,
5243
5470
  writeFileSync as writeFileSync3
5244
5471
  } from "fs";
5245
- import { homedir as homedir3 } from "os";
5246
- import { dirname as dirname5, posix, win32 } from "path";
5472
+ import { homedir as homedir4 } from "os";
5473
+ import { dirname as dirname6, posix, win32 } from "path";
5247
5474
  import { z as z2 } from "zod";
5248
5475
  var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
5249
5476
  var cachedModelOptionSchema = z2.object({
@@ -5263,7 +5490,7 @@ var modelsDevCacheSchema = z2.object({
5263
5490
  function resolveModelsDevCachePath(options = {}) {
5264
5491
  const platform = options.platform ?? process.platform;
5265
5492
  const env = options.env ?? process.env;
5266
- const home = options.homeDir ?? homedir3();
5493
+ const home = options.homeDir ?? homedir4();
5267
5494
  if (platform === "win32") {
5268
5495
  const path = win32;
5269
5496
  const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
@@ -5273,7 +5500,7 @@ function resolveModelsDevCachePath(options = {}) {
5273
5500
  return posix.join(base, "thoth-agents", "models-dev-v1.json");
5274
5501
  }
5275
5502
  function readModelsDevCache(cachePath) {
5276
- if (!existsSync10(cachePath)) return void 0;
5503
+ if (!existsSync11(cachePath)) return void 0;
5277
5504
  try {
5278
5505
  const input = JSON.parse(readFileSync6(cachePath, "utf8"));
5279
5506
  const parsed = modelsDevCacheSchema.safeParse(input);
@@ -5287,7 +5514,7 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
5287
5514
  if (!validated.success) {
5288
5515
  throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
5289
5516
  }
5290
- mkdirSync3(dirname5(cachePath), { recursive: true });
5517
+ mkdirSync4(dirname6(cachePath), { recursive: true });
5291
5518
  const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
5292
5519
  try {
5293
5520
  writeFileSync3(
@@ -5295,9 +5522,9 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
5295
5522
  `${JSON.stringify(validated.data, null, 2)}
5296
5523
  `
5297
5524
  );
5298
- renameSync2(temporaryPath, cachePath);
5525
+ renameSync3(temporaryPath, cachePath);
5299
5526
  } catch (error) {
5300
- if (existsSync10(temporaryPath)) unlinkSync(temporaryPath);
5527
+ if (existsSync11(temporaryPath)) unlinkSync(temporaryPath);
5301
5528
  throw error;
5302
5529
  }
5303
5530
  }
@@ -5610,7 +5837,7 @@ async function getModelOptions(harness) {
5610
5837
  }
5611
5838
 
5612
5839
  // src/cli/tui/operations.ts
5613
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
5840
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
5614
5841
  var context = { cwd: process.cwd() };
5615
5842
  var codexContext = { cwd: process.cwd() };
5616
5843
  var claudeCodeContext = {
@@ -5652,7 +5879,7 @@ function getCodexModelRoles(source = codexContext) {
5652
5879
  const item = plan.items.find(
5653
5880
  (candidate) => candidate.action === "write-role-toml" && candidate.role === role
5654
5881
  );
5655
- const content = item && existsSync11(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : item?.content;
5882
+ const content = item && existsSync12(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : item?.content;
5656
5883
  const effort = content ? parseRoleTomlEffort(content) : void 0;
5657
5884
  return {
5658
5885
  role,
@@ -5688,9 +5915,10 @@ function getOpenCodeModelRoles() {
5688
5915
  const activePreset = selectedPreset && typeof selectedPreset === "object" && !Array.isArray(selectedPreset) ? selectedPreset : void 0;
5689
5916
  return ALL_AGENT_NAMES.map((role) => {
5690
5917
  const defaultModel = getDefaultOpenCodeModel(role);
5691
- const model = readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model") ?? defaultModel;
5918
+ const configuredModel = readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model");
5919
+ const model = configuredModel ?? defaultModel;
5692
5920
  const configuredVariant = readRoleField(agents, role, "variant") ?? readRoleField(activePreset, role, "variant");
5693
- const variant = configuredVariant ?? (model === defaultModel ? getDefaultOpenCodeVariant(role) : void 0);
5921
+ const variant = configuredVariant ?? (configuredModel === void 0 ? getDefaultOpenCodeVariant(role) : void 0);
5694
5922
  return {
5695
5923
  role,
5696
5924
  model,
@@ -5774,6 +6002,7 @@ export {
5774
6002
  buildClaudeCodeSetupPlan,
5775
6003
  applyClaudeCodeSetup,
5776
6004
  formatClaudeCodeSetupPlan,
6005
+ syncOpenCodeOwnedSkills,
5777
6006
  getNpxCommand,
5778
6007
  REQUIRED_SKILLS,
5779
6008
  getRequiredSkillInstallCommand,