oh-my-claudecode-opencode 0.6.11 → 0.7.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.
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}`);
@@ -0,0 +1,49 @@
1
+ import type { CategoryConfig } from "./types";
2
+ /**
3
+ * Prompt append for visual-engineering category.
4
+ * Encourages bold design choices and distinctive aesthetics.
5
+ */
6
+ export declare const VISUAL_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on VISUAL/UI tasks.\n\nDesign-first mindset:\n- Bold aesthetic choices over safe defaults\n- Unexpected layouts, asymmetry, grid-breaking elements\n- Distinctive typography (avoid: Arial, Inter, Roboto, Space Grotesk)\n- Cohesive color palettes with sharp accents\n- High-impact animations with staggered reveals\n- Atmosphere: gradient meshes, noise textures, layered transparencies\n\nAVOID: Generic fonts, purple gradients on white, predictable layouts, cookie-cutter patterns.\n</Category_Context>";
7
+ /**
8
+ * Prompt append for ultrabrain category.
9
+ * Encourages strategic thinking and architectural clarity.
10
+ */
11
+ export declare const ULTRABRAIN_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on COMPLEX ARCHITECTURE / DEEP REASONING tasks.\n\nStrategic advisor mindset:\n- Bias toward simplicity: least complex solution that fulfills requirements\n- Leverage existing code/patterns over new components\n- Prioritize developer experience and maintainability\n- One clear recommendation with effort estimate (Quick/Short/Medium/Large)\n- Signal when advanced approach warranted\n\nResponse format:\n- Bottom line (2-3 sentences)\n- Action plan (numbered steps)\n- Risks and mitigations (if relevant)\n</Category_Context>";
12
+ /**
13
+ * Prompt append for artistry category.
14
+ * Encourages radical creativity and unconventional approaches.
15
+ */
16
+ export declare const ARTISTRY_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on HIGHLY CREATIVE / ARTISTIC tasks.\n\nArtistic genius mindset:\n- Push far beyond conventional boundaries\n- Explore radical, unconventional directions\n- Surprise and delight: unexpected twists, novel combinations\n- Rich detail and vivid expression\n- Break patterns deliberately when it serves the creative vision\n\nApproach:\n- Generate diverse, bold options first\n- Embrace ambiguity and wild experimentation\n- Balance novelty with coherence\n- This is for tasks requiring exceptional creativity\n</Category_Context>";
17
+ /**
18
+ * Prompt append for quick category.
19
+ * Optimized for fast, focused execution with less capable models.
20
+ */
21
+ export declare const QUICK_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on SMALL / QUICK tasks.\n\nEfficient execution mindset:\n- Fast, focused, minimal overhead\n- Get to the point immediately\n- No over-engineering\n- Simple solutions for simple problems\n\nApproach:\n- Minimal viable implementation\n- Skip unnecessary abstractions\n- Direct and concise\n</Category_Context>\n\n<Caller_Warning>\nTHIS CATEGORY USES A LESS CAPABLE MODEL (haiku tier).\n\nThe model executing this task has LIMITED reasoning capacity. Your prompt MUST be:\n\n**EXHAUSTIVELY EXPLICIT** - Leave NOTHING to interpretation:\n1. MUST DO: List every required action as atomic, numbered steps\n2. MUST NOT DO: Explicitly forbid likely mistakes and deviations\n3. EXPECTED OUTPUT: Describe exact success criteria with concrete examples\n\n**WHY THIS MATTERS:**\n- Less capable models WILL deviate without explicit guardrails\n- Vague instructions \u2192 unpredictable results\n- Implicit expectations \u2192 missed requirements\n\n**PROMPT STRUCTURE (MANDATORY):**\n```\nTASK: [One-sentence goal]\n\nMUST DO:\n1. [Specific action with exact details]\n2. [Another specific action]\n...\n\nMUST NOT DO:\n- [Forbidden action + why]\n- [Another forbidden action]\n...\n\nEXPECTED OUTPUT:\n- [Exact deliverable description]\n- [Success criteria / verification method]\n```\n\nIf your prompt lacks this structure, REWRITE IT before delegating.\n</Caller_Warning>";
22
+ /**
23
+ * Prompt append for unspecified-low category.
24
+ * For moderate-effort tasks that don't fit specific categories.
25
+ */
26
+ export declare const UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on tasks that don't fit specific categories but require moderate effort.\n\n<Selection_Gate>\nBEFORE selecting this category, VERIFY ALL conditions:\n1. Task does NOT fit: quick (trivial), visual-engineering (UI), ultrabrain (deep logic), artistry (creative), writing (docs)\n2. Task requires more than trivial effort but is NOT system-wide\n3. Scope is contained within a few files/modules\n\nIf task fits ANY other category, DO NOT select unspecified-low.\nThis is NOT a default choice - it's for genuinely unclassifiable moderate-effort work.\n</Selection_Gate>\n</Category_Context>\n\n<Caller_Warning>\nTHIS CATEGORY USES A MID-TIER MODEL (sonnet tier).\n\n**PROVIDE CLEAR STRUCTURE:**\n1. MUST DO: Enumerate required actions explicitly\n2. MUST NOT DO: State forbidden actions to prevent scope creep\n3. EXPECTED OUTPUT: Define concrete success criteria\n</Caller_Warning>";
27
+ /**
28
+ * Prompt append for unspecified-high category.
29
+ * For high-effort tasks that don't fit specific categories.
30
+ */
31
+ export declare const UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on tasks that don't fit specific categories but require substantial effort.\n\n<Selection_Gate>\nBEFORE selecting this category, VERIFY ALL conditions:\n1. Task does NOT fit: quick (trivial), visual-engineering (UI), ultrabrain (deep logic), artistry (creative), writing (docs)\n2. Task requires substantial effort across multiple systems/modules\n3. Changes have broad impact or require careful coordination\n4. NOT just \"complex\" - must be genuinely unclassifiable AND high-effort\n\nIf task fits ANY other category, DO NOT select unspecified-high.\nIf task is unclassifiable but moderate-effort, use unspecified-low instead.\n</Selection_Gate>\n</Category_Context>";
32
+ /**
33
+ * Prompt append for writing category.
34
+ * Optimized for documentation and prose tasks.
35
+ */
36
+ export declare const WRITING_CATEGORY_PROMPT_APPEND = "<Category_Context>\nYou are working on WRITING / PROSE tasks.\n\nWordsmith mindset:\n- Clear, flowing prose\n- Appropriate tone and voice\n- Engaging and readable\n- Proper structure and organization\n\nApproach:\n- Understand the audience\n- Draft with care\n- Polish for clarity and impact\n- Documentation, READMEs, articles, technical writing\n</Category_Context>";
37
+ /**
38
+ * Default category configurations.
39
+ * Uses abstract tier names (haiku/sonnet/opus) that will be resolved to actual provider/model by the model resolution service.
40
+ */
41
+ export declare const DEFAULT_CATEGORIES: Record<string, CategoryConfig>;
42
+ /**
43
+ * Map of category names to their prompt appends.
44
+ */
45
+ export declare const CATEGORY_PROMPT_APPENDS: Record<string, string>;
46
+ /**
47
+ * Map of category names to their human-readable descriptions.
48
+ */
49
+ export declare const CATEGORY_DESCRIPTIONS: Record<string, string>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Categories module for OMCO.
3
+ *
4
+ * Categories provide semantic task classification that automatically maps to:
5
+ * - Model tier (haiku/sonnet/opus)
6
+ * - Temperature settings
7
+ * - Thinking budget (via variant)
8
+ * - Specialized prompt context
9
+ *
10
+ * This allows calling code to delegate tasks by semantic meaning rather than
11
+ * worrying about technical model selection details.
12
+ */
13
+ export type { CategoryConfig, CategoriesConfig } from "./types";
14
+ export { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, VISUAL_CATEGORY_PROMPT_APPEND, ULTRABRAIN_CATEGORY_PROMPT_APPEND, ARTISTRY_CATEGORY_PROMPT_APPEND, QUICK_CATEGORY_PROMPT_APPEND, UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND, UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND, WRITING_CATEGORY_PROMPT_APPEND } from "./constants";
15
+ export type { ResolvedCategory } from "./resolver";
16
+ export { resolveCategoryConfig, getAvailableCategories } from "./resolver";
@@ -0,0 +1,15 @@
1
+ import type { CategoryConfig, CategoriesConfig } from "./types";
2
+ export interface ResolvedCategory {
3
+ config: CategoryConfig;
4
+ promptAppend: string;
5
+ tier: string;
6
+ }
7
+ /**
8
+ * Resolve a category name to its configuration.
9
+ * Priority: user override > default category
10
+ */
11
+ export declare function resolveCategoryConfig(categoryName: string, userCategories?: CategoriesConfig): ResolvedCategory | null;
12
+ /**
13
+ * Get list of all available category names.
14
+ */
15
+ export declare function getAvailableCategories(userCategories?: CategoriesConfig): string[];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Configuration for a delegation category.
3
+ * Categories determine which model tier and behavior profile to use for delegated tasks.
4
+ */
5
+ export interface CategoryConfig {
6
+ /** Abstract model tier name (haiku/sonnet/opus) - resolved by model resolution service */
7
+ model?: string;
8
+ /** Model variant for extended thinking budget */
9
+ variant?: "low" | "medium" | "high" | "max" | "xhigh";
10
+ /** Human-readable description of what this category is for */
11
+ description?: string;
12
+ /** Additional prompt context appended when using this category */
13
+ prompt_append?: string;
14
+ /** Flag indicating this agent configuration is experimental/unstable */
15
+ is_unstable_agent?: boolean;
16
+ }
17
+ /**
18
+ * Map of category names to their configurations.
19
+ */
20
+ export type CategoriesConfig = Record<string, CategoryConfig>;
@@ -96,6 +96,32 @@ declare const AutopilotConfigSchema: z.ZodObject<{
96
96
  off: "off";
97
97
  }>>;
98
98
  }, z.core.$strip>;
99
+ export declare const CategoryConfigSchema: z.ZodObject<{
100
+ model: z.ZodOptional<z.ZodString>;
101
+ variant: z.ZodOptional<z.ZodEnum<{
102
+ low: "low";
103
+ medium: "medium";
104
+ high: "high";
105
+ max: "max";
106
+ xhigh: "xhigh";
107
+ }>>;
108
+ description: z.ZodOptional<z.ZodString>;
109
+ prompt_append: z.ZodOptional<z.ZodString>;
110
+ is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
111
+ }, z.core.$strip>;
112
+ export declare const CategoriesConfigSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
113
+ model: z.ZodOptional<z.ZodString>;
114
+ variant: z.ZodOptional<z.ZodEnum<{
115
+ low: "low";
116
+ medium: "medium";
117
+ high: "high";
118
+ max: "max";
119
+ xhigh: "xhigh";
120
+ }>>;
121
+ description: z.ZodOptional<z.ZodString>;
122
+ prompt_append: z.ZodOptional<z.ZodString>;
123
+ is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
124
+ }, z.core.$strip>>;
99
125
  declare const UltraQAConfigSchema: z.ZodObject<{
100
126
  enabled: z.ZodOptional<z.ZodBoolean>;
101
127
  maxIterations: z.ZodOptional<z.ZodNumber>;
@@ -274,6 +300,19 @@ declare const OmoOmcsConfigSchema: z.ZodObject<{
274
300
  toastDuration: z.ZodOptional<z.ZodNumber>;
275
301
  trackMetrics: z.ZodOptional<z.ZodBoolean>;
276
302
  }, z.core.$strip>>;
303
+ categories: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
304
+ model: z.ZodOptional<z.ZodString>;
305
+ variant: z.ZodOptional<z.ZodEnum<{
306
+ low: "low";
307
+ medium: "medium";
308
+ high: "high";
309
+ max: "max";
310
+ xhigh: "xhigh";
311
+ }>>;
312
+ description: z.ZodOptional<z.ZodString>;
313
+ prompt_append: z.ZodOptional<z.ZodString>;
314
+ is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
315
+ }, z.core.$strip>>>;
277
316
  omco_agent: z.ZodOptional<z.ZodObject<{
278
317
  disabled: z.ZodOptional<z.ZodBoolean>;
279
318
  planner_enabled: z.ZodOptional<z.ZodBoolean>;
@@ -296,6 +335,8 @@ export type McpServersConfig = z.infer<typeof McpServersConfigSchema>;
296
335
  export type PermissionsConfig = z.infer<typeof PermissionsConfigSchema>;
297
336
  export type MagicKeywordsConfig = z.infer<typeof MagicKeywordsConfigSchema>;
298
337
  export type RoutingConfig = z.infer<typeof RoutingConfigSchema>;
338
+ export type CategoryConfig = z.infer<typeof CategoryConfigSchema>;
339
+ export type CategoriesConfig = z.infer<typeof CategoriesConfigSchema>;
299
340
  export type HookName = "todo-continuation-enforcer" | "keyword-detector" | "ralph-loop" | "session-recovery" | "agent-usage-reminder" | "context-window-monitor" | "comment-checker" | "tool-output-truncator" | "system-prompt-injector" | "persistent-mode" | "remember-tag-processor" | "autopilot" | "ultraqa-loop" | "context-recovery" | "edit-error-recovery" | "omc-orchestrator";
300
341
  export type AgentName = "omc" | "architect" | "researcher" | "explore" | "frontendEngineer" | "documentWriter" | "multimodalLooker" | "critic" | "analyst" | "planner" | "oracle" | "librarian" | "frontend-ui-ux-engineer" | "document-writer" | "multimodal-looker";
301
342
  export declare function loadConfig(directory: string): OmoOmcsConfig;
package/dist/index.js CHANGED
@@ -20592,6 +20592,14 @@ var AutopilotConfigSchema = exports_external.object({
20592
20592
  maxPhaseRetries: exports_external.number().min(1).max(10).optional(),
20593
20593
  delegationEnforcement: exports_external.enum(["strict", "warn", "off"]).optional()
20594
20594
  });
20595
+ var CategoryConfigSchema = exports_external.object({
20596
+ model: exports_external.string().optional(),
20597
+ variant: exports_external.enum(["low", "medium", "high", "max", "xhigh"]).optional(),
20598
+ description: exports_external.string().optional(),
20599
+ prompt_append: exports_external.string().optional(),
20600
+ is_unstable_agent: exports_external.boolean().optional()
20601
+ });
20602
+ var CategoriesConfigSchema = exports_external.record(exports_external.string(), CategoryConfigSchema);
20595
20603
  var UltraQAConfigSchema = exports_external.object({
20596
20604
  enabled: exports_external.boolean().optional(),
20597
20605
  maxIterations: exports_external.number().min(1).max(100).optional(),
@@ -20644,6 +20652,7 @@ var OmoOmcsConfigSchema = exports_external.object({
20644
20652
  context_recovery: ContextRecoveryConfigSchema.optional(),
20645
20653
  edit_error_recovery: EditErrorRecoveryConfigSchema.optional(),
20646
20654
  tui_status: TuiStatusConfigSchema.optional(),
20655
+ categories: CategoriesConfigSchema.optional(),
20647
20656
  omco_agent: exports_external.object({
20648
20657
  disabled: exports_external.boolean().optional(),
20649
20658
  planner_enabled: exports_external.boolean().optional(),
@@ -20674,16 +20683,16 @@ function loadConfig(directory) {
20674
20683
  }
20675
20684
  return {
20676
20685
  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 }
20686
+ omc: { tier: "opus", enabled: true },
20687
+ architect: { tier: "opus", enabled: true },
20688
+ researcher: { tier: "sonnet", enabled: true },
20689
+ explore: { tier: "haiku", enabled: true },
20690
+ frontendEngineer: { tier: "sonnet", enabled: true },
20691
+ documentWriter: { tier: "haiku", enabled: true },
20692
+ multimodalLooker: { tier: "sonnet", enabled: true },
20693
+ critic: { tier: "opus", enabled: true },
20694
+ analyst: { tier: "opus", enabled: true },
20695
+ planner: { tier: "opus", enabled: true }
20687
20696
  },
20688
20697
  features: {
20689
20698
  parallelExecution: true,
@@ -20714,11 +20723,6 @@ function loadConfig(directory) {
20714
20723
  defaultTier: "MEDIUM",
20715
20724
  escalationEnabled: true,
20716
20725
  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
20726
  agentOverrides: {
20723
20727
  architect: { tier: "HIGH", reason: "Advisory agent requires deep reasoning" },
20724
20728
  planner: { tier: "HIGH", reason: "Strategic planning requires deep reasoning" },
@@ -20770,7 +20774,8 @@ function loadConfig(directory) {
20770
20774
  },
20771
20775
  omco_agent: {
20772
20776
  disabled: false
20773
- }
20777
+ },
20778
+ categories: {}
20774
20779
  };
20775
20780
  }
20776
20781
 
@@ -21873,6 +21878,29 @@ function createBackgroundManager(ctx, config2, modelService) {
21873
21878
  }
21874
21879
  return count;
21875
21880
  };
21881
+ const detectConfiguredProvider = async () => {
21882
+ try {
21883
+ const providersResp = await ctx.client.provider.list({
21884
+ query: { directory: ctx.directory }
21885
+ });
21886
+ const providers = providersResp.data;
21887
+ for (const provider of providers || []) {
21888
+ if (provider.models && provider.models.length > 0) {
21889
+ const modelConfig = {
21890
+ providerID: provider.id,
21891
+ modelID: provider.models[0].id
21892
+ };
21893
+ log(`Detected configured provider as fallback`, { providerID: modelConfig.providerID, modelID: modelConfig.modelID });
21894
+ return modelConfig;
21895
+ }
21896
+ }
21897
+ log(`No configured providers found`);
21898
+ return;
21899
+ } catch (err) {
21900
+ log(`Failed to detect configured provider`, { error: String(err) });
21901
+ return;
21902
+ }
21903
+ };
21876
21904
  const getParentSessionModel = async (parentSessionID) => {
21877
21905
  if (modelCache.has(parentSessionID)) {
21878
21906
  return modelCache.get(parentSessionID);
@@ -21894,11 +21922,17 @@ function createBackgroundManager(ctx, config2, modelService) {
21894
21922
  return model;
21895
21923
  }
21896
21924
  log(`Parent session has no assistant messages with model info`, { parentSessionID });
21897
- return;
21898
21925
  } catch (err) {
21899
21926
  log(`Failed to get parent session model`, { parentSessionID, error: String(err) });
21900
- return;
21901
21927
  }
21928
+ try {
21929
+ const configuredModel = await detectConfiguredProvider();
21930
+ if (configuredModel) {
21931
+ modelCache.set(parentSessionID, configuredModel);
21932
+ return configuredModel;
21933
+ }
21934
+ } catch {}
21935
+ return;
21902
21936
  };
21903
21937
  const createTask = async (parentSessionID, description, prompt, agent, model) => {
21904
21938
  const runningCount = getRunningCount();
@@ -21906,109 +21940,127 @@ function createBackgroundManager(ctx, config2, modelService) {
21906
21940
  throw new Error(`Max concurrent tasks (${defaultConcurrency}) reached. Wait for some to complete.`);
21907
21941
  }
21908
21942
  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 }
21943
+ try {
21944
+ const parentModel = model || await getParentSessionModel(parentSessionID);
21945
+ const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
21946
+ if (!resolvedModel) {
21947
+ throw new Error(`[OMCO] No model available for agent "${agent}". ` + `Configure tier mapping with 'npx omco-setup'.`);
21948
+ }
21949
+ if (resolvedModel !== parentModel) {
21950
+ log(`[background-manager] Using tier-mapped model for ${agent}`, {
21951
+ providerID: resolvedModel.providerID,
21952
+ modelID: resolvedModel.modelID
21934
21953
  });
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}
21954
+ }
21955
+ const task = {
21956
+ id: taskId,
21957
+ status: "running",
21958
+ description,
21959
+ parentSessionID,
21960
+ startedAt: Date.now()
21961
+ };
21962
+ tasks.set(taskId, task);
21963
+ log(`Background task created`, { taskId, description, agent });
21964
+ (async () => {
21965
+ try {
21966
+ const sessionResp = await ctx.client.session.create({
21967
+ body: {
21968
+ parentID: parentSessionID,
21969
+ title: `${agent}: ${description}`
21970
+ },
21971
+ query: { directory: ctx.directory }
21972
+ });
21973
+ const sessionID = sessionResp.data?.id ?? sessionResp.id;
21974
+ if (!sessionID)
21975
+ throw new Error("Failed to create session");
21976
+ task.sessionID = sessionID;
21977
+ const canonicalName = isAlias(agent) ? getCanonicalName(agent) : agent;
21978
+ const agentDef = getAgent(canonicalName);
21979
+ const systemPrompt = agentDef?.systemPrompt || "";
21980
+ const fullPrompt = systemPrompt ? `${systemPrompt}
21943
21981
 
21944
21982
  ${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)}`);
21983
+ const promptBody = {
21984
+ parts: [{ type: "text", text: fullPrompt }]
21985
+ };
21986
+ if (resolvedModel) {
21987
+ promptBody.model = resolvedModel;
21988
+ log(`Using model for subagent`, { taskId, ...resolvedModel });
21989
+ }
21990
+ let promptResp = await ctx.client.session.prompt({
21991
+ path: { id: sessionID },
21992
+ body: promptBody,
21993
+ query: { directory: ctx.directory }
21994
+ });
21995
+ let promptData = promptResp.data;
21996
+ if (promptResp.error) {
21997
+ throw new Error(`Prompt failed: ${JSON.stringify(promptResp.error)}`);
21998
+ }
21999
+ if (promptData?.info?.error) {
22000
+ const err = promptData.info.error;
22001
+ const isModelError = err.name === "ProviderModelNotFoundError" || err.name === "ProviderNotFoundError" || err.name?.includes("Model") || err.name?.includes("Provider");
22002
+ if (isModelError && parentModel && resolvedModel !== parentModel) {
22003
+ log(`[background-manager] Model error with tier-mapped model, retrying with parent session model`, {
22004
+ taskId,
22005
+ error: err.name,
22006
+ failedModel: resolvedModel,
22007
+ fallbackModel: parentModel
22008
+ });
22009
+ promptBody.model = parentModel;
22010
+ promptResp = await ctx.client.session.prompt({
22011
+ path: { id: sessionID },
22012
+ body: promptBody,
22013
+ query: { directory: ctx.directory }
22014
+ });
22015
+ promptData = promptResp.data;
22016
+ if (promptResp.error) {
22017
+ throw new Error(`Prompt failed after retry: ${JSON.stringify(promptResp.error)}`);
22018
+ }
21980
22019
  }
21981
22020
  }
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
22021
+ if (promptData?.info?.error) {
22022
+ const err = promptData.info.error;
22023
+ const errMsg = err.data?.message || err.name || "Unknown error";
22024
+ throw new Error(`[${err.name}] ${errMsg}`);
22000
22025
  }
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;
22026
+ const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
22027
+ `) || "";
22028
+ task.result = result;
22029
+ task.status = "completed";
22030
+ task.completedAt = Date.now();
22031
+ log(`Background task completed`, { taskId, duration: task.completedAt - task.startedAt });
22032
+ ctx.client.tui.showToast({
22033
+ body: {
22034
+ title: "Background Task Completed",
22035
+ message: `${description.substring(0, 40)}...`,
22036
+ variant: "success",
22037
+ duration: 3000
22038
+ }
22039
+ }).catch((err) => {
22040
+ log(`Toast notification failed`, { taskId, error: String(err) });
22041
+ });
22042
+ } catch (err) {
22043
+ task.status = "failed";
22044
+ task.error = String(err);
22045
+ task.completedAt = Date.now();
22046
+ log(`Background task failed`, { taskId, error: task.error });
22047
+ }
22048
+ })();
22049
+ return task;
22050
+ } catch (err) {
22051
+ const failedTask = {
22052
+ id: taskId,
22053
+ status: "failed",
22054
+ description,
22055
+ parentSessionID,
22056
+ error: String(err),
22057
+ startedAt: Date.now(),
22058
+ completedAt: Date.now()
22059
+ };
22060
+ tasks.set(taskId, failedTask);
22061
+ log(`Background task failed during creation`, { taskId, error: String(err) });
22062
+ return failedTask;
22063
+ }
22012
22064
  };
22013
22065
  const getTask = (taskId) => {
22014
22066
  return tasks.get(taskId);
@@ -34475,14 +34527,243 @@ Use \`background_output\` to get results. Prompts MUST be in English.`,
34475
34527
  };
34476
34528
  }
34477
34529
 
34530
+ // src/categories/constants.ts
34531
+ var VISUAL_CATEGORY_PROMPT_APPEND = `<Category_Context>
34532
+ You are working on VISUAL/UI tasks.
34533
+
34534
+ Design-first mindset:
34535
+ - Bold aesthetic choices over safe defaults
34536
+ - Unexpected layouts, asymmetry, grid-breaking elements
34537
+ - Distinctive typography (avoid: Arial, Inter, Roboto, Space Grotesk)
34538
+ - Cohesive color palettes with sharp accents
34539
+ - High-impact animations with staggered reveals
34540
+ - Atmosphere: gradient meshes, noise textures, layered transparencies
34541
+
34542
+ AVOID: Generic fonts, purple gradients on white, predictable layouts, cookie-cutter patterns.
34543
+ </Category_Context>`;
34544
+ var ULTRABRAIN_CATEGORY_PROMPT_APPEND = `<Category_Context>
34545
+ You are working on COMPLEX ARCHITECTURE / DEEP REASONING tasks.
34546
+
34547
+ Strategic advisor mindset:
34548
+ - Bias toward simplicity: least complex solution that fulfills requirements
34549
+ - Leverage existing code/patterns over new components
34550
+ - Prioritize developer experience and maintainability
34551
+ - One clear recommendation with effort estimate (Quick/Short/Medium/Large)
34552
+ - Signal when advanced approach warranted
34553
+
34554
+ Response format:
34555
+ - Bottom line (2-3 sentences)
34556
+ - Action plan (numbered steps)
34557
+ - Risks and mitigations (if relevant)
34558
+ </Category_Context>`;
34559
+ var ARTISTRY_CATEGORY_PROMPT_APPEND = `<Category_Context>
34560
+ You are working on HIGHLY CREATIVE / ARTISTIC tasks.
34561
+
34562
+ Artistic genius mindset:
34563
+ - Push far beyond conventional boundaries
34564
+ - Explore radical, unconventional directions
34565
+ - Surprise and delight: unexpected twists, novel combinations
34566
+ - Rich detail and vivid expression
34567
+ - Break patterns deliberately when it serves the creative vision
34568
+
34569
+ Approach:
34570
+ - Generate diverse, bold options first
34571
+ - Embrace ambiguity and wild experimentation
34572
+ - Balance novelty with coherence
34573
+ - This is for tasks requiring exceptional creativity
34574
+ </Category_Context>`;
34575
+ var QUICK_CATEGORY_PROMPT_APPEND = `<Category_Context>
34576
+ You are working on SMALL / QUICK tasks.
34577
+
34578
+ Efficient execution mindset:
34579
+ - Fast, focused, minimal overhead
34580
+ - Get to the point immediately
34581
+ - No over-engineering
34582
+ - Simple solutions for simple problems
34583
+
34584
+ Approach:
34585
+ - Minimal viable implementation
34586
+ - Skip unnecessary abstractions
34587
+ - Direct and concise
34588
+ </Category_Context>
34589
+
34590
+ <Caller_Warning>
34591
+ THIS CATEGORY USES A LESS CAPABLE MODEL (haiku tier).
34592
+
34593
+ The model executing this task has LIMITED reasoning capacity. Your prompt MUST be:
34594
+
34595
+ **EXHAUSTIVELY EXPLICIT** - Leave NOTHING to interpretation:
34596
+ 1. MUST DO: List every required action as atomic, numbered steps
34597
+ 2. MUST NOT DO: Explicitly forbid likely mistakes and deviations
34598
+ 3. EXPECTED OUTPUT: Describe exact success criteria with concrete examples
34599
+
34600
+ **WHY THIS MATTERS:**
34601
+ - Less capable models WILL deviate without explicit guardrails
34602
+ - Vague instructions \u2192 unpredictable results
34603
+ - Implicit expectations \u2192 missed requirements
34604
+
34605
+ **PROMPT STRUCTURE (MANDATORY):**
34606
+ \`\`\`
34607
+ TASK: [One-sentence goal]
34608
+
34609
+ MUST DO:
34610
+ 1. [Specific action with exact details]
34611
+ 2. [Another specific action]
34612
+ ...
34613
+
34614
+ MUST NOT DO:
34615
+ - [Forbidden action + why]
34616
+ - [Another forbidden action]
34617
+ ...
34618
+
34619
+ EXPECTED OUTPUT:
34620
+ - [Exact deliverable description]
34621
+ - [Success criteria / verification method]
34622
+ \`\`\`
34623
+
34624
+ If your prompt lacks this structure, REWRITE IT before delegating.
34625
+ </Caller_Warning>`;
34626
+ var UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND = `<Category_Context>
34627
+ You are working on tasks that don't fit specific categories but require moderate effort.
34628
+
34629
+ <Selection_Gate>
34630
+ BEFORE selecting this category, VERIFY ALL conditions:
34631
+ 1. Task does NOT fit: quick (trivial), visual-engineering (UI), ultrabrain (deep logic), artistry (creative), writing (docs)
34632
+ 2. Task requires more than trivial effort but is NOT system-wide
34633
+ 3. Scope is contained within a few files/modules
34634
+
34635
+ If task fits ANY other category, DO NOT select unspecified-low.
34636
+ This is NOT a default choice - it's for genuinely unclassifiable moderate-effort work.
34637
+ </Selection_Gate>
34638
+ </Category_Context>
34639
+
34640
+ <Caller_Warning>
34641
+ THIS CATEGORY USES A MID-TIER MODEL (sonnet tier).
34642
+
34643
+ **PROVIDE CLEAR STRUCTURE:**
34644
+ 1. MUST DO: Enumerate required actions explicitly
34645
+ 2. MUST NOT DO: State forbidden actions to prevent scope creep
34646
+ 3. EXPECTED OUTPUT: Define concrete success criteria
34647
+ </Caller_Warning>`;
34648
+ var UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND = `<Category_Context>
34649
+ You are working on tasks that don't fit specific categories but require substantial effort.
34650
+
34651
+ <Selection_Gate>
34652
+ BEFORE selecting this category, VERIFY ALL conditions:
34653
+ 1. Task does NOT fit: quick (trivial), visual-engineering (UI), ultrabrain (deep logic), artistry (creative), writing (docs)
34654
+ 2. Task requires substantial effort across multiple systems/modules
34655
+ 3. Changes have broad impact or require careful coordination
34656
+ 4. NOT just "complex" - must be genuinely unclassifiable AND high-effort
34657
+
34658
+ If task fits ANY other category, DO NOT select unspecified-high.
34659
+ If task is unclassifiable but moderate-effort, use unspecified-low instead.
34660
+ </Selection_Gate>
34661
+ </Category_Context>`;
34662
+ var WRITING_CATEGORY_PROMPT_APPEND = `<Category_Context>
34663
+ You are working on WRITING / PROSE tasks.
34664
+
34665
+ Wordsmith mindset:
34666
+ - Clear, flowing prose
34667
+ - Appropriate tone and voice
34668
+ - Engaging and readable
34669
+ - Proper structure and organization
34670
+
34671
+ Approach:
34672
+ - Understand the audience
34673
+ - Draft with care
34674
+ - Polish for clarity and impact
34675
+ - Documentation, READMEs, articles, technical writing
34676
+ </Category_Context>`;
34677
+ var DEFAULT_CATEGORIES = {
34678
+ "visual-engineering": {
34679
+ model: "opus",
34680
+ description: "Frontend, UI/UX, design, styling, animation"
34681
+ },
34682
+ ultrabrain: {
34683
+ model: "opus",
34684
+ variant: "high",
34685
+ description: "Deep logical reasoning, complex architecture decisions requiring extensive analysis"
34686
+ },
34687
+ artistry: {
34688
+ model: "opus",
34689
+ variant: "max",
34690
+ description: "Highly creative/artistic tasks, novel ideas"
34691
+ },
34692
+ quick: {
34693
+ model: "haiku",
34694
+ description: "Trivial tasks - single file changes, typo fixes, simple modifications"
34695
+ },
34696
+ "unspecified-low": {
34697
+ model: "sonnet",
34698
+ description: "Tasks that don't fit other categories, moderate effort required"
34699
+ },
34700
+ "unspecified-high": {
34701
+ model: "opus",
34702
+ variant: "max",
34703
+ description: "Tasks that don't fit other categories, high effort required"
34704
+ },
34705
+ writing: {
34706
+ model: "sonnet",
34707
+ description: "Documentation, prose, technical writing"
34708
+ }
34709
+ };
34710
+ var CATEGORY_PROMPT_APPENDS = {
34711
+ "visual-engineering": VISUAL_CATEGORY_PROMPT_APPEND,
34712
+ ultrabrain: ULTRABRAIN_CATEGORY_PROMPT_APPEND,
34713
+ artistry: ARTISTRY_CATEGORY_PROMPT_APPEND,
34714
+ quick: QUICK_CATEGORY_PROMPT_APPEND,
34715
+ "unspecified-low": UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND,
34716
+ "unspecified-high": UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND,
34717
+ writing: WRITING_CATEGORY_PROMPT_APPEND
34718
+ };
34719
+ var CATEGORY_DESCRIPTIONS = {
34720
+ "visual-engineering": "Frontend, UI/UX, design, styling, animation",
34721
+ ultrabrain: "Deep logical reasoning, complex architecture decisions requiring extensive analysis",
34722
+ artistry: "Highly creative/artistic tasks, novel ideas",
34723
+ quick: "Trivial tasks - single file changes, typo fixes, simple modifications",
34724
+ "unspecified-low": "Tasks that don't fit other categories, moderate effort required",
34725
+ "unspecified-high": "Tasks that don't fit other categories, high effort required",
34726
+ writing: "Documentation, prose, technical writing"
34727
+ };
34728
+ // src/categories/resolver.ts
34729
+ function resolveCategoryConfig(categoryName, userCategories) {
34730
+ const defaultConfig = DEFAULT_CATEGORIES[categoryName];
34731
+ const userConfig = userCategories?.[categoryName];
34732
+ const defaultPromptAppend = CATEGORY_PROMPT_APPENDS[categoryName] ?? "";
34733
+ if (!defaultConfig && !userConfig) {
34734
+ return null;
34735
+ }
34736
+ const config3 = {
34737
+ ...defaultConfig,
34738
+ ...userConfig
34739
+ };
34740
+ const tier = config3.model ?? "sonnet";
34741
+ let promptAppend = defaultPromptAppend;
34742
+ if (userConfig?.prompt_append) {
34743
+ promptAppend = defaultPromptAppend ? defaultPromptAppend + `
34744
+
34745
+ ` + userConfig.prompt_append : userConfig.prompt_append;
34746
+ }
34747
+ return { config: config3, promptAppend, tier };
34748
+ }
34749
+ function getAvailableCategories(userCategories) {
34750
+ const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
34751
+ return Object.keys(allCategories);
34752
+ }
34478
34753
  // src/tools/call-omco-agent.ts
34479
- function createCallOmcoAgent(ctx, manager, modelService) {
34754
+ function createCallOmcoAgent(ctx, manager, modelService, userCategories) {
34480
34755
  const agentNames = listAgentNames();
34481
34756
  const agentList = agentNames.map((name) => {
34482
34757
  const agent = getAgent(name);
34483
34758
  const aliasNote = isAlias(name) ? ` (alias for ${getCanonicalName(name)})` : "";
34484
34759
  return `- ${name}${aliasNote}: ${agent?.description || "Agent"}`;
34485
34760
  }).join(`
34761
+ `);
34762
+ const categoryNames = getAvailableCategories(userCategories);
34763
+ const categoryList = categoryNames.map((name) => {
34764
+ const desc = CATEGORY_DESCRIPTIONS[name] || userCategories?.[name]?.description || "Category";
34765
+ return `- ${name}: ${desc}`;
34766
+ }).join(`
34486
34767
  `);
34487
34768
  return tool({
34488
34769
  description: `Spawn specialized agent for delegation. run_in_background REQUIRED (true=async with task_id, false=sync).
@@ -34490,38 +34771,119 @@ function createCallOmcoAgent(ctx, manager, modelService) {
34490
34771
  Available agents:
34491
34772
  ${agentList}
34492
34773
 
34774
+ Available categories:
34775
+ ${categoryList}
34776
+
34493
34777
  Prompts MUST be in English. Use \`background_output\` for async results.`,
34494
34778
  args: {
34495
34779
  description: tool.schema.string().describe("Short description of task"),
34496
34780
  prompt: tool.schema.string().describe("Task prompt"),
34497
- subagent_type: tool.schema.string().describe(`Agent type to spawn. Available: ${agentNames.join(", ")}`),
34781
+ subagent_type: tool.schema.string().optional().describe(`Agent type to spawn. Available: ${agentNames.join(", ")}`),
34782
+ category: tool.schema.string().optional().describe(`Category for delegation (e.g., 'quick', 'visual-engineering', 'ultrabrain'). Mutually exclusive with subagent_type.`),
34498
34783
  run_in_background: tool.schema.boolean().describe("Run async (true) or sync (false)"),
34499
34784
  session_id: tool.schema.string().optional().describe("Existing session to continue")
34500
34785
  },
34501
34786
  async execute(args, context) {
34502
- const { description, prompt, subagent_type, run_in_background } = args;
34503
- const agent = getAgent(subagent_type);
34504
- if (!agent) {
34787
+ const { description, prompt, subagent_type, category, run_in_background } = args;
34788
+ if (subagent_type && category) {
34789
+ return JSON.stringify({
34790
+ status: "failed",
34791
+ error: "subagent_type and category are mutually exclusive. Provide only one."
34792
+ });
34793
+ }
34794
+ if (!subagent_type && !category) {
34505
34795
  return JSON.stringify({
34506
34796
  status: "failed",
34507
- error: `Unknown agent type: ${subagent_type}. Available: ${listAgentNames().join(", ")}`
34797
+ error: "Either subagent_type or category must be provided."
34508
34798
  });
34509
34799
  }
34510
- const enhancedPrompt = `${agent.systemPrompt}
34800
+ let enhancedPrompt;
34801
+ let tierForResolution;
34802
+ let agentTypeForLogging;
34803
+ if (category) {
34804
+ const resolved = resolveCategoryConfig(category, userCategories);
34805
+ if (!resolved) {
34806
+ return JSON.stringify({
34807
+ status: "failed",
34808
+ error: `Unknown category: ${category}. Available: ${getAvailableCategories(userCategories).join(", ")}`
34809
+ });
34810
+ }
34811
+ enhancedPrompt = resolved.promptAppend ? `${resolved.promptAppend}
34812
+
34813
+ ---
34814
+
34815
+ ${prompt}` : prompt;
34816
+ tierForResolution = resolved.tier;
34817
+ agentTypeForLogging = `category:${category}`;
34818
+ log(`[call-omco-agent] Using category delegation`, { category, tier: tierForResolution });
34819
+ } else {
34820
+ const agent = getAgent(subagent_type);
34821
+ if (!agent) {
34822
+ return JSON.stringify({
34823
+ status: "failed",
34824
+ error: `Unknown agent type: ${subagent_type}. Available: ${listAgentNames().join(", ")}`
34825
+ });
34826
+ }
34827
+ enhancedPrompt = `${agent.systemPrompt}
34511
34828
 
34512
34829
  ---
34513
34830
 
34514
34831
  ${prompt}`;
34832
+ agentTypeForLogging = subagent_type;
34833
+ }
34515
34834
  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
- });
34835
+ let resolvedModel = parentModel;
34836
+ if (modelService) {
34837
+ try {
34838
+ if (category && tierForResolution) {
34839
+ const categoryModel = modelService.resolveModelForCategory(tierForResolution, parentModel);
34840
+ if (categoryModel) {
34841
+ resolvedModel = categoryModel;
34842
+ if (resolvedModel && resolvedModel !== parentModel) {
34843
+ log(`[call-omco-agent] Using tier-mapped model for category ${category}`, {
34844
+ tier: tierForResolution,
34845
+ providerID: resolvedModel.providerID,
34846
+ modelID: resolvedModel.modelID
34847
+ });
34848
+ }
34849
+ } else if (!parentModel) {
34850
+ const errorMsg = `[OMCO] Cannot resolve model for category "${category}" (tier: ${tierForResolution}).
34851
+
34852
+ ` + `No tier mapping configured. Run one of:
34853
+ ` + ` 1. npx omco-setup (interactive setup)
34854
+ ` + ` 2. Add tierDefaults to ~/.config/opencode/omco.json:
34855
+ ` + ` {
34856
+ ` + ` "model_mapping": {
34857
+ ` + ` "tierDefaults": {
34858
+ ` + ` "haiku": "openai/gpt-4o-mini",
34859
+ ` + ` "sonnet": "openai/gpt-4o",
34860
+ ` + ` "opus": "openai/o1"
34861
+ ` + ` }
34862
+ ` + ` }
34863
+ ` + ` }`;
34864
+ return JSON.stringify({
34865
+ status: "failed",
34866
+ error: errorMsg
34867
+ });
34868
+ }
34869
+ } else if (subagent_type) {
34870
+ resolvedModel = modelService.resolveModelForAgentOrThrow(subagent_type, parentModel);
34871
+ if (resolvedModel && resolvedModel !== parentModel) {
34872
+ log(`[call-omco-agent] Using tier-mapped model for ${subagent_type}`, {
34873
+ providerID: resolvedModel.providerID,
34874
+ modelID: resolvedModel.modelID
34875
+ });
34876
+ }
34877
+ }
34878
+ } catch (err) {
34879
+ return JSON.stringify({
34880
+ status: "failed",
34881
+ error: err instanceof Error ? err.message : String(err)
34882
+ });
34883
+ }
34522
34884
  }
34523
34885
  if (run_in_background) {
34524
- const task = await manager.createTask(context.sessionID, description, enhancedPrompt, subagent_type, resolvedModel);
34886
+ const task = await manager.createTask(context.sessionID, description, enhancedPrompt, agentTypeForLogging, resolvedModel);
34525
34887
  return JSON.stringify({
34526
34888
  task_id: task.id,
34527
34889
  session_id: task.sessionID,
@@ -34533,7 +34895,7 @@ ${prompt}`;
34533
34895
  const sessionResp = await ctx.client.session.create({
34534
34896
  body: {
34535
34897
  parentID: context.sessionID,
34536
- title: `${subagent_type}: ${description}`
34898
+ title: `${agentTypeForLogging}: ${description}`
34537
34899
  },
34538
34900
  query: { directory: ctx.directory }
34539
34901
  });
@@ -34545,7 +34907,7 @@ ${prompt}`;
34545
34907
  };
34546
34908
  if (resolvedModel) {
34547
34909
  promptBody.model = resolvedModel;
34548
- log(`Using resolved model for sync agent call`, { subagent_type, ...resolvedModel });
34910
+ log(`Using resolved model for sync agent call`, { agentType: agentTypeForLogging, ...resolvedModel });
34549
34911
  }
34550
34912
  let promptResp = await ctx.client.session.prompt({
34551
34913
  path: { id: sessionID },
@@ -34735,12 +35097,64 @@ function createModelResolutionService(modelMappingConfig, agentOverrides) {
34735
35097
  }
34736
35098
  return fallbackModel;
34737
35099
  };
35100
+ const resolveModelForAgentOrThrow = (agentName, fallbackModel) => {
35101
+ const result = resolveModelForAgent(agentName, fallbackModel);
35102
+ if (result)
35103
+ return result;
35104
+ const tierDefaults2 = resolver.getTierDefaults();
35105
+ const hasConfiguredTiers2 = Object.values(tierDefaults2).some((m) => m.includes("/"));
35106
+ let errorMessage = `[OMCO] Cannot resolve model for agent "${agentName}".`;
35107
+ if (!hasConfiguredTiers2) {
35108
+ errorMessage += `
35109
+
35110
+ No tier mapping configured. Run one of:
35111
+ ` + ` 1. npx omco-setup (interactive setup)
35112
+ ` + ` 2. Add tierDefaults to ~/.config/opencode/omco.json:
35113
+ ` + ` {
35114
+ ` + ` "model_mapping": {
35115
+ ` + ` "tierDefaults": {
35116
+ ` + ` "haiku": "openai/gpt-4o-mini",
35117
+ ` + ` "sonnet": "openai/gpt-4o",
35118
+ ` + ` "opus": "openai/o1"
35119
+ ` + ` }
35120
+ ` + ` }
35121
+ ` + ` }`;
35122
+ } else {
35123
+ errorMessage += `
35124
+
35125
+ Tier mapping is configured but no fallback model available.
35126
+ ` + `This usually means the parent session hasn't started yet.
35127
+ ` + `Try sending a message first to establish the session model.`;
35128
+ }
35129
+ throw new Error(errorMessage);
35130
+ };
34738
35131
  const isTierMappingConfigured = () => {
34739
35132
  return hasConfiguredTiers;
34740
35133
  };
35134
+ const resolveModelForCategory = (categoryTier, fallbackModel) => {
35135
+ const tierDefaults2 = resolver.getTierDefaults();
35136
+ const mappedModel = tierDefaults2[categoryTier];
35137
+ if (mappedModel) {
35138
+ const modelConfig = parseModelString(mappedModel);
35139
+ if (modelConfig) {
35140
+ if (debugLogging) {
35141
+ log(`[model-resolution] Resolved category tier "${categoryTier}": ${mappedModel}`);
35142
+ }
35143
+ return modelConfig;
35144
+ }
35145
+ }
35146
+ if (debugLogging) {
35147
+ log(`[model-resolution] No mapping for category tier "${categoryTier}", using fallback`, {
35148
+ fallback: fallbackModel ? `${fallbackModel.providerID}/${fallbackModel.modelID}` : "none"
35149
+ });
35150
+ }
35151
+ return fallbackModel;
35152
+ };
34741
35153
  return {
34742
35154
  resolveModelForAgent,
34743
- isTierMappingConfigured
35155
+ resolveModelForAgentOrThrow,
35156
+ isTierMappingConfigured,
35157
+ resolveModelForCategory
34744
35158
  };
34745
35159
  }
34746
35160
 
@@ -38635,7 +39049,7 @@ var OmoOmcsPlugin = async (ctx) => {
38635
39049
  }
38636
39050
  const backgroundManager = createBackgroundManager(ctx, pluginConfig.background_task, modelService);
38637
39051
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
38638
- const callOmcoAgent = createCallOmcoAgent(ctx, backgroundManager, modelService);
39052
+ const callOmcoAgent = createCallOmcoAgent(ctx, backgroundManager, modelService, pluginConfig.categories);
38639
39053
  const systemPromptInjector = createSystemPromptInjector(ctx);
38640
39054
  const skillInjector = createSkillInjector(ctx);
38641
39055
  const ralphLoop = createRalphLoopHook(ctx, {
@@ -1,4 +1,5 @@
1
1
  import { type PluginInput, type ToolDefinition } from "@opencode-ai/plugin";
2
2
  import type { BackgroundManager } from "./background-manager";
3
3
  import type { ModelResolutionService } from "./model-resolution-service";
4
- export declare function createCallOmcoAgent(ctx: PluginInput, manager: BackgroundManager, modelService?: ModelResolutionService): ToolDefinition;
4
+ import type { CategoriesConfig } from "../categories/types";
5
+ export declare function createCallOmcoAgent(ctx: PluginInput, manager: BackgroundManager, modelService?: ModelResolutionService, userCategories?: CategoriesConfig): ToolDefinition;
@@ -27,10 +27,25 @@ 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
  */
33
41
  isTierMappingConfigured(): boolean;
42
+ /**
43
+ * Resolve model for a category-based tier (abstract tier name)
44
+ * @param categoryTier - Abstract tier name (haiku, sonnet, opus)
45
+ * @param fallbackModel - Parent session model to use if resolution fails
46
+ * @returns Resolved ModelConfig or undefined if no mapping found
47
+ */
48
+ resolveModelForCategory(categoryTier: string, fallbackModel?: ModelConfig): ModelConfig | undefined;
34
49
  }
35
50
  /**
36
51
  * Create a ModelResolutionService instance
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.7.0",
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",