oh-my-claudecode-opencode 0.6.11 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/omco-setup.js CHANGED
@@ -87,6 +87,16 @@ async function main() {
87
87
  };
88
88
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + `
89
89
  `);
90
+ try {
91
+ const written = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
92
+ if (written.model_mapping?.tierDefaults) {
93
+ console.log(`
94
+ ✅ Config verification passed.`);
95
+ }
96
+ } catch {
97
+ console.error(`
98
+ ⚠️ Warning: Could not verify written config.`);
99
+ }
90
100
  console.log(`
91
101
  ✅ Configured tier mapping for ${provider}:
92
102
  `);
package/bin/omco-setup.ts CHANGED
@@ -105,6 +105,16 @@ async function main() {
105
105
  // Write config
106
106
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
107
107
 
108
+ // Verify config can be read back
109
+ try {
110
+ const written = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
111
+ if (written.model_mapping?.tierDefaults) {
112
+ console.log(`\n✅ Config verification passed.`);
113
+ }
114
+ } catch {
115
+ console.error(`\n⚠️ Warning: Could not verify written config.`);
116
+ }
117
+
108
118
  console.log(`\n✅ Configured tier mapping for ${provider}:\n`);
109
119
  console.log(` haiku → ${tiers.haiku}`);
110
120
  console.log(` sonnet → ${tiers.sonnet}`);
package/dist/index.js CHANGED
@@ -20674,16 +20674,16 @@ function loadConfig(directory) {
20674
20674
  }
20675
20675
  return {
20676
20676
  agents: {
20677
- omc: { model: "github-copilot/claude-opus-4", enabled: true },
20678
- architect: { model: "github-copilot/claude-opus-4", enabled: true },
20679
- researcher: { model: "github-copilot/claude-sonnet-4", enabled: true },
20680
- explore: { model: "github-copilot/claude-haiku-4", enabled: true },
20681
- frontendEngineer: { model: "github-copilot/claude-sonnet-4", enabled: true },
20682
- documentWriter: { model: "github-copilot/claude-haiku-4", enabled: true },
20683
- multimodalLooker: { model: "github-copilot/claude-sonnet-4", enabled: true },
20684
- critic: { model: "github-copilot/claude-opus-4", enabled: true },
20685
- analyst: { model: "github-copilot/claude-opus-4", enabled: true },
20686
- planner: { model: "github-copilot/claude-opus-4", enabled: true }
20677
+ omc: { tier: "opus", enabled: true },
20678
+ architect: { tier: "opus", enabled: true },
20679
+ researcher: { tier: "sonnet", enabled: true },
20680
+ explore: { tier: "haiku", enabled: true },
20681
+ frontendEngineer: { tier: "sonnet", enabled: true },
20682
+ documentWriter: { tier: "haiku", enabled: true },
20683
+ multimodalLooker: { tier: "sonnet", enabled: true },
20684
+ critic: { tier: "opus", enabled: true },
20685
+ analyst: { tier: "opus", enabled: true },
20686
+ planner: { tier: "opus", enabled: true }
20687
20687
  },
20688
20688
  features: {
20689
20689
  parallelExecution: true,
@@ -20714,11 +20714,6 @@ function loadConfig(directory) {
20714
20714
  defaultTier: "MEDIUM",
20715
20715
  escalationEnabled: true,
20716
20716
  maxEscalations: 2,
20717
- tierModels: {
20718
- LOW: "github-copilot/claude-haiku-4",
20719
- MEDIUM: "github-copilot/claude-sonnet-4",
20720
- HIGH: "github-copilot/claude-opus-4"
20721
- },
20722
20717
  agentOverrides: {
20723
20718
  architect: { tier: "HIGH", reason: "Advisory agent requires deep reasoning" },
20724
20719
  planner: { tier: "HIGH", reason: "Strategic planning requires deep reasoning" },
@@ -21873,6 +21868,29 @@ function createBackgroundManager(ctx, config2, modelService) {
21873
21868
  }
21874
21869
  return count;
21875
21870
  };
21871
+ const detectConfiguredProvider = async () => {
21872
+ try {
21873
+ const providersResp = await ctx.client.provider.list({
21874
+ query: { directory: ctx.directory }
21875
+ });
21876
+ const providers = providersResp.data;
21877
+ for (const provider of providers || []) {
21878
+ if (provider.models && provider.models.length > 0) {
21879
+ const modelConfig = {
21880
+ providerID: provider.id,
21881
+ modelID: provider.models[0].id
21882
+ };
21883
+ log(`Detected configured provider as fallback`, { providerID: modelConfig.providerID, modelID: modelConfig.modelID });
21884
+ return modelConfig;
21885
+ }
21886
+ }
21887
+ log(`No configured providers found`);
21888
+ return;
21889
+ } catch (err) {
21890
+ log(`Failed to detect configured provider`, { error: String(err) });
21891
+ return;
21892
+ }
21893
+ };
21876
21894
  const getParentSessionModel = async (parentSessionID) => {
21877
21895
  if (modelCache.has(parentSessionID)) {
21878
21896
  return modelCache.get(parentSessionID);
@@ -21894,11 +21912,17 @@ function createBackgroundManager(ctx, config2, modelService) {
21894
21912
  return model;
21895
21913
  }
21896
21914
  log(`Parent session has no assistant messages with model info`, { parentSessionID });
21897
- return;
21898
21915
  } catch (err) {
21899
21916
  log(`Failed to get parent session model`, { parentSessionID, error: String(err) });
21900
- return;
21901
21917
  }
21918
+ try {
21919
+ const configuredModel = await detectConfiguredProvider();
21920
+ if (configuredModel) {
21921
+ modelCache.set(parentSessionID, configuredModel);
21922
+ return configuredModel;
21923
+ }
21924
+ } catch {}
21925
+ return;
21902
21926
  };
21903
21927
  const createTask = async (parentSessionID, description, prompt, agent, model) => {
21904
21928
  const runningCount = getRunningCount();
@@ -21906,109 +21930,127 @@ function createBackgroundManager(ctx, config2, modelService) {
21906
21930
  throw new Error(`Max concurrent tasks (${defaultConcurrency}) reached. Wait for some to complete.`);
21907
21931
  }
21908
21932
  const taskId = generateTaskId();
21909
- const task = {
21910
- id: taskId,
21911
- status: "running",
21912
- description,
21913
- parentSessionID,
21914
- startedAt: Date.now()
21915
- };
21916
- tasks.set(taskId, task);
21917
- log(`Background task created`, { taskId, description, agent });
21918
- const parentModel = model || await getParentSessionModel(parentSessionID);
21919
- const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
21920
- if (resolvedModel && resolvedModel !== parentModel) {
21921
- log(`[background-manager] Using tier-mapped model for ${agent}`, {
21922
- providerID: resolvedModel.providerID,
21923
- modelID: resolvedModel.modelID
21924
- });
21925
- }
21926
- (async () => {
21927
- try {
21928
- const sessionResp = await ctx.client.session.create({
21929
- body: {
21930
- parentID: parentSessionID,
21931
- title: `${agent}: ${description}`
21932
- },
21933
- query: { directory: ctx.directory }
21933
+ try {
21934
+ const parentModel = model || await getParentSessionModel(parentSessionID);
21935
+ const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
21936
+ if (!resolvedModel) {
21937
+ throw new Error(`[OMCO] No model available for agent "${agent}". ` + `Configure tier mapping with 'npx omco-setup'.`);
21938
+ }
21939
+ if (resolvedModel !== parentModel) {
21940
+ log(`[background-manager] Using tier-mapped model for ${agent}`, {
21941
+ providerID: resolvedModel.providerID,
21942
+ modelID: resolvedModel.modelID
21934
21943
  });
21935
- const sessionID = sessionResp.data?.id ?? sessionResp.id;
21936
- if (!sessionID)
21937
- throw new Error("Failed to create session");
21938
- task.sessionID = sessionID;
21939
- const canonicalName = isAlias(agent) ? getCanonicalName(agent) : agent;
21940
- const agentDef = getAgent(canonicalName);
21941
- const systemPrompt = agentDef?.systemPrompt || "";
21942
- const fullPrompt = systemPrompt ? `${systemPrompt}
21944
+ }
21945
+ const task = {
21946
+ id: taskId,
21947
+ status: "running",
21948
+ description,
21949
+ parentSessionID,
21950
+ startedAt: Date.now()
21951
+ };
21952
+ tasks.set(taskId, task);
21953
+ log(`Background task created`, { taskId, description, agent });
21954
+ (async () => {
21955
+ try {
21956
+ const sessionResp = await ctx.client.session.create({
21957
+ body: {
21958
+ parentID: parentSessionID,
21959
+ title: `${agent}: ${description}`
21960
+ },
21961
+ query: { directory: ctx.directory }
21962
+ });
21963
+ const sessionID = sessionResp.data?.id ?? sessionResp.id;
21964
+ if (!sessionID)
21965
+ throw new Error("Failed to create session");
21966
+ task.sessionID = sessionID;
21967
+ const canonicalName = isAlias(agent) ? getCanonicalName(agent) : agent;
21968
+ const agentDef = getAgent(canonicalName);
21969
+ const systemPrompt = agentDef?.systemPrompt || "";
21970
+ const fullPrompt = systemPrompt ? `${systemPrompt}
21943
21971
 
21944
21972
  ${prompt}` : prompt;
21945
- const promptBody = {
21946
- parts: [{ type: "text", text: fullPrompt }]
21947
- };
21948
- if (resolvedModel) {
21949
- promptBody.model = resolvedModel;
21950
- log(`Using model for subagent`, { taskId, ...resolvedModel });
21951
- }
21952
- let promptResp = await ctx.client.session.prompt({
21953
- path: { id: sessionID },
21954
- body: promptBody,
21955
- query: { directory: ctx.directory }
21956
- });
21957
- let promptData = promptResp.data;
21958
- if (promptResp.error) {
21959
- throw new Error(`Prompt failed: ${JSON.stringify(promptResp.error)}`);
21960
- }
21961
- if (promptData?.info?.error) {
21962
- const err = promptData.info.error;
21963
- const isModelError = err.name === "ProviderModelNotFoundError" || err.name === "ProviderNotFoundError" || err.name?.includes("Model") || err.name?.includes("Provider");
21964
- if (isModelError && parentModel && resolvedModel !== parentModel) {
21965
- log(`[background-manager] Model error with tier-mapped model, retrying with parent session model`, {
21966
- taskId,
21967
- error: err.name,
21968
- failedModel: resolvedModel,
21969
- fallbackModel: parentModel
21970
- });
21971
- promptBody.model = parentModel;
21972
- promptResp = await ctx.client.session.prompt({
21973
- path: { id: sessionID },
21974
- body: promptBody,
21975
- query: { directory: ctx.directory }
21976
- });
21977
- promptData = promptResp.data;
21978
- if (promptResp.error) {
21979
- throw new Error(`Prompt failed after retry: ${JSON.stringify(promptResp.error)}`);
21973
+ const promptBody = {
21974
+ parts: [{ type: "text", text: fullPrompt }]
21975
+ };
21976
+ if (resolvedModel) {
21977
+ promptBody.model = resolvedModel;
21978
+ log(`Using model for subagent`, { taskId, ...resolvedModel });
21979
+ }
21980
+ let promptResp = await ctx.client.session.prompt({
21981
+ path: { id: sessionID },
21982
+ body: promptBody,
21983
+ query: { directory: ctx.directory }
21984
+ });
21985
+ let promptData = promptResp.data;
21986
+ if (promptResp.error) {
21987
+ throw new Error(`Prompt failed: ${JSON.stringify(promptResp.error)}`);
21988
+ }
21989
+ if (promptData?.info?.error) {
21990
+ const err = promptData.info.error;
21991
+ const isModelError = err.name === "ProviderModelNotFoundError" || err.name === "ProviderNotFoundError" || err.name?.includes("Model") || err.name?.includes("Provider");
21992
+ if (isModelError && parentModel && resolvedModel !== parentModel) {
21993
+ log(`[background-manager] Model error with tier-mapped model, retrying with parent session model`, {
21994
+ taskId,
21995
+ error: err.name,
21996
+ failedModel: resolvedModel,
21997
+ fallbackModel: parentModel
21998
+ });
21999
+ promptBody.model = parentModel;
22000
+ promptResp = await ctx.client.session.prompt({
22001
+ path: { id: sessionID },
22002
+ body: promptBody,
22003
+ query: { directory: ctx.directory }
22004
+ });
22005
+ promptData = promptResp.data;
22006
+ if (promptResp.error) {
22007
+ throw new Error(`Prompt failed after retry: ${JSON.stringify(promptResp.error)}`);
22008
+ }
21980
22009
  }
21981
22010
  }
21982
- }
21983
- if (promptData?.info?.error) {
21984
- const err = promptData.info.error;
21985
- const errMsg = err.data?.message || err.name || "Unknown error";
21986
- throw new Error(`[${err.name}] ${errMsg}`);
21987
- }
21988
- const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
21989
- `) || "";
21990
- task.result = result;
21991
- task.status = "completed";
21992
- task.completedAt = Date.now();
21993
- log(`Background task completed`, { taskId, duration: task.completedAt - task.startedAt });
21994
- ctx.client.tui.showToast({
21995
- body: {
21996
- title: "Background Task Completed",
21997
- message: `${description.substring(0, 40)}...`,
21998
- variant: "success",
21999
- duration: 3000
22011
+ if (promptData?.info?.error) {
22012
+ const err = promptData.info.error;
22013
+ const errMsg = err.data?.message || err.name || "Unknown error";
22014
+ throw new Error(`[${err.name}] ${errMsg}`);
22000
22015
  }
22001
- }).catch((err) => {
22002
- log(`Toast notification failed`, { taskId, error: String(err) });
22003
- });
22004
- } catch (err) {
22005
- task.status = "failed";
22006
- task.error = String(err);
22007
- task.completedAt = Date.now();
22008
- log(`Background task failed`, { taskId, error: task.error });
22009
- }
22010
- })();
22011
- return task;
22016
+ const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
22017
+ `) || "";
22018
+ task.result = result;
22019
+ task.status = "completed";
22020
+ task.completedAt = Date.now();
22021
+ log(`Background task completed`, { taskId, duration: task.completedAt - task.startedAt });
22022
+ ctx.client.tui.showToast({
22023
+ body: {
22024
+ title: "Background Task Completed",
22025
+ message: `${description.substring(0, 40)}...`,
22026
+ variant: "success",
22027
+ duration: 3000
22028
+ }
22029
+ }).catch((err) => {
22030
+ log(`Toast notification failed`, { taskId, error: String(err) });
22031
+ });
22032
+ } catch (err) {
22033
+ task.status = "failed";
22034
+ task.error = String(err);
22035
+ task.completedAt = Date.now();
22036
+ log(`Background task failed`, { taskId, error: task.error });
22037
+ }
22038
+ })();
22039
+ return task;
22040
+ } catch (err) {
22041
+ const failedTask = {
22042
+ id: taskId,
22043
+ status: "failed",
22044
+ description,
22045
+ parentSessionID,
22046
+ error: String(err),
22047
+ startedAt: Date.now(),
22048
+ completedAt: Date.now()
22049
+ };
22050
+ tasks.set(taskId, failedTask);
22051
+ log(`Background task failed during creation`, { taskId, error: String(err) });
22052
+ return failedTask;
22053
+ }
22012
22054
  };
22013
22055
  const getTask = (taskId) => {
22014
22056
  return tasks.get(taskId);
@@ -34513,12 +34555,22 @@ Prompts MUST be in English. Use \`background_output\` for async results.`,
34513
34555
 
34514
34556
  ${prompt}`;
34515
34557
  const parentModel = await manager.getParentSessionModel(context.sessionID);
34516
- const resolvedModel = modelService ? modelService.resolveModelForAgent(subagent_type, parentModel) : parentModel;
34517
- if (resolvedModel && resolvedModel !== parentModel) {
34518
- log(`[call-omco-agent] Using tier-mapped model for ${subagent_type}`, {
34519
- providerID: resolvedModel.providerID,
34520
- modelID: resolvedModel.modelID
34521
- });
34558
+ let resolvedModel = parentModel;
34559
+ if (modelService) {
34560
+ try {
34561
+ resolvedModel = modelService.resolveModelForAgentOrThrow(subagent_type, parentModel);
34562
+ if (resolvedModel && resolvedModel !== parentModel) {
34563
+ log(`[call-omco-agent] Using tier-mapped model for ${subagent_type}`, {
34564
+ providerID: resolvedModel.providerID,
34565
+ modelID: resolvedModel.modelID
34566
+ });
34567
+ }
34568
+ } catch (err) {
34569
+ return JSON.stringify({
34570
+ status: "failed",
34571
+ error: err instanceof Error ? err.message : String(err)
34572
+ });
34573
+ }
34522
34574
  }
34523
34575
  if (run_in_background) {
34524
34576
  const task = await manager.createTask(context.sessionID, description, enhancedPrompt, subagent_type, resolvedModel);
@@ -34735,11 +34787,43 @@ function createModelResolutionService(modelMappingConfig, agentOverrides) {
34735
34787
  }
34736
34788
  return fallbackModel;
34737
34789
  };
34790
+ const resolveModelForAgentOrThrow = (agentName, fallbackModel) => {
34791
+ const result = resolveModelForAgent(agentName, fallbackModel);
34792
+ if (result)
34793
+ return result;
34794
+ const tierDefaults2 = resolver.getTierDefaults();
34795
+ const hasConfiguredTiers2 = Object.values(tierDefaults2).some((m) => m.includes("/"));
34796
+ let errorMessage = `[OMCO] Cannot resolve model for agent "${agentName}".`;
34797
+ if (!hasConfiguredTiers2) {
34798
+ errorMessage += `
34799
+
34800
+ No tier mapping configured. Run one of:
34801
+ ` + ` 1. npx omco-setup (interactive setup)
34802
+ ` + ` 2. Add tierDefaults to ~/.config/opencode/omco.json:
34803
+ ` + ` {
34804
+ ` + ` "model_mapping": {
34805
+ ` + ` "tierDefaults": {
34806
+ ` + ` "haiku": "openai/gpt-4o-mini",
34807
+ ` + ` "sonnet": "openai/gpt-4o",
34808
+ ` + ` "opus": "openai/o1"
34809
+ ` + ` }
34810
+ ` + ` }
34811
+ ` + ` }`;
34812
+ } else {
34813
+ errorMessage += `
34814
+
34815
+ Tier mapping is configured but no fallback model available.
34816
+ ` + `This usually means the parent session hasn't started yet.
34817
+ ` + `Try sending a message first to establish the session model.`;
34818
+ }
34819
+ throw new Error(errorMessage);
34820
+ };
34738
34821
  const isTierMappingConfigured = () => {
34739
34822
  return hasConfiguredTiers;
34740
34823
  };
34741
34824
  return {
34742
34825
  resolveModelForAgent,
34826
+ resolveModelForAgentOrThrow,
34743
34827
  isTierMappingConfigured
34744
34828
  };
34745
34829
  }
@@ -27,6 +27,14 @@ export interface ModelResolutionService {
27
27
  * @returns Resolved ModelConfig or undefined if should use fallback
28
28
  */
29
29
  resolveModelForAgent(agentName: string, fallbackModel?: ModelConfig): ModelConfig | undefined;
30
+ /**
31
+ * Resolve model for an agent, always returning a result or throwing
32
+ * @param agentName - Name of the agent (canonical or alias)
33
+ * @param fallbackModel - Parent session model to use if resolution fails
34
+ * @returns Resolved ModelConfig (never undefined)
35
+ * @throws Error with actionable message if model cannot be resolved
36
+ */
37
+ resolveModelForAgentOrThrow(agentName: string, fallbackModel?: ModelConfig): ModelConfig;
30
38
  /**
31
39
  * Check if tier mapping is configured (tierDefaults has provider/model format)
32
40
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-claudecode-opencode",
3
- "version": "0.6.11",
3
+ "version": "0.6.12",
4
4
  "description": "OpenCode port of oh-my-claudecode - Multi-agent orchestration plugin (omco)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",