Browse Source

cleanup: remove dead code, dead schema fields, barrel files, dedup shared logic

adikpb 1 month ago
parent
commit
73298a908d

+ 0 - 258
oh-my-opencode-slim.schema.json

@@ -8,197 +8,10 @@
     "setDefaultAgent": {
       "type": "boolean"
     },
-    "scoringEngineVersion": {
-      "type": "string",
-      "enum": [
-        "v1",
-        "v2-shadow",
-        "v2"
-      ]
-    },
-    "balanceProviderUsage": {
-      "type": "boolean"
-    },
     "autoUpdate": {
       "description": "Disable automatic installation of plugin updates when false. Defaults to true.",
       "type": "boolean"
     },
-    "manualPlan": {
-      "type": "object",
-      "properties": {
-        "orchestrator": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        },
-        "oracle": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        },
-        "designer": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        },
-        "explorer": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        },
-        "librarian": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        },
-        "fixer": {
-          "type": "object",
-          "properties": {
-            "primary": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback1": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback2": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            },
-            "fallback3": {
-              "type": "string",
-              "pattern": "^[^/\\s]+\\/[^\\s]+$"
-            }
-          },
-          "required": [
-            "primary",
-            "fallback1",
-            "fallback2",
-            "fallback3"
-          ]
-        }
-      },
-      "required": [
-        "orchestrator",
-        "oracle",
-        "designer",
-        "explorer",
-        "librarian",
-        "fixer"
-      ],
-      "additionalProperties": false
-    },
     "presets": {
       "type": "object",
       "propertyNames": {
@@ -524,71 +337,6 @@
           "default": true,
           "type": "boolean"
         },
-        "timeoutMs": {
-          "default": 15000,
-          "type": "number",
-          "minimum": 0
-        },
-        "retryDelayMs": {
-          "default": 500,
-          "type": "number",
-          "minimum": 0
-        },
-        "chains": {
-          "default": {},
-          "type": "object",
-          "properties": {
-            "orchestrator": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            },
-            "oracle": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            },
-            "designer": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            },
-            "explorer": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            },
-            "librarian": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            },
-            "fixer": {
-              "minItems": 1,
-              "type": "array",
-              "items": {
-                "type": "string"
-              }
-            }
-          },
-          "additionalProperties": {
-            "minItems": 1,
-            "type": "array",
-            "items": {
-              "type": "string"
-            }
-          }
-        },
         "retry_on_empty": {
           "default": true,
           "description": "When true (default), empty provider responses are treated as failures, triggering fallback/retry. Set to false to treat them as successes.",
@@ -645,12 +393,6 @@
         },
         "master": {
           "description": "DEPRECATED — ignored. Council agent synthesizes directly."
-        },
-        "master_timeout": {
-          "description": "DEPRECATED — ignored. Use \"timeout\" instead."
-        },
-        "master_fallback": {
-          "description": "DEPRECATED — ignored. No separate master session."
         }
       },
       "required": [

+ 0 - 3
src/agents/orchestrator.ts

@@ -264,9 +264,6 @@ When user's approach seems problematic:
 `;
 }
 
-/** @deprecated Use buildOrchestratorPrompt() instead */
-export const ORCHESTRATOR_PROMPT = buildOrchestratorPrompt();
-
 export function createOrchestratorAgent(
   model?: string | Array<string | { id: string; variant?: string }>,
   customPrompt?: string,

+ 1 - 15
src/config/agent-mcps.ts

@@ -1,9 +1,4 @@
-import {
-  type AgentName,
-  getAgentOverride,
-  McpNameSchema,
-  type PluginConfig,
-} from '.';
+import { type AgentName, getAgentOverride, type PluginConfig } from '.';
 
 /** Default MCPs per agent - "*" means all MCPs, "!item" excludes specific MCPs */
 
@@ -43,15 +38,6 @@ export function parseList(items: string[], allAvailable: string[]): string[] {
   );
 }
 
-/**
- * Get available MCP names from schema and config.
- */
-export function getAvailableMcpNames(config?: PluginConfig): string[] {
-  const builtinMcps = McpNameSchema.options;
-  const disabled = new Set(config?.disabled_mcps ?? []);
-  return builtinMcps.filter((name) => !disabled.has(name));
-}
-
 /**
  * Get the MCP list for an agent (from config or defaults).
  */

+ 1 - 42
src/config/constants.ts

@@ -15,31 +15,11 @@ export const SUBAGENT_NAMES = [
   'councillor',
 ] as const;
 
-export const ORCHESTRATOR_NAME = 'orchestrator' as const;
-
-export const ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES] as const;
+export const ALL_AGENT_NAMES = ['orchestrator', ...SUBAGENT_NAMES] as const;
 
 // Agent name type (for use in DEFAULT_MODELS)
 export type AgentName = (typeof ALL_AGENT_NAMES)[number];
 
-// Subagent delegation rules: which agents can spawn which subagents
-// orchestrator: can spawn all subagents (full delegation)
-// fixer: leaf node — prompt forbids delegation; use grep/glob for lookups
-// designer: can spawn explorer (for research during design)
-// explorer/librarian/oracle: cannot spawn any subagents (leaf nodes)
-// Unknown agent types not listed here default to explorer-only access
-// Which agents each agent type can spawn via delegation.
-// councillor is internal — only CouncilManager spawns it.
-export const ORCHESTRATABLE_AGENTS = [
-  'explorer',
-  'librarian',
-  'oracle',
-  'designer',
-  'fixer',
-  'observer',
-  'council',
-] as const;
-
 /** Agents that cannot be disabled even if listed in disabled_agents config. */
 export const PROTECTED_AGENTS = new Set(['orchestrator', 'councillor']);
 
@@ -47,24 +27,6 @@ export const PROTECTED_AGENTS = new Set(['orchestrator', 'councillor']);
  * Get the list of orchestratable agents, excluding any disabled agents.
  * This is used for delegation validation at runtime.
  */
-export function getOrchestratableAgents(
-  disabledAgents?: Set<string>,
-): string[] {
-  return ORCHESTRATABLE_AGENTS.filter((name) => !disabledAgents?.has(name));
-}
-
-export const SUBAGENT_DELEGATION_RULES: Record<AgentName, readonly string[]> = {
-  orchestrator: ORCHESTRATABLE_AGENTS,
-  fixer: [],
-  designer: [],
-  explorer: [],
-  librarian: [],
-  oracle: [],
-  observer: [],
-  council: [],
-  councillor: [],
-};
-
 // Default models for each agent
 // orchestrator is undefined so its model is fully resolved at runtime via priority fallback
 export const DEFAULT_MODELS: Record<AgentName, string | undefined> = {
@@ -81,13 +43,10 @@ export const DEFAULT_MODELS: Record<AgentName, string | undefined> = {
 
 // Polling configuration
 export const POLL_INTERVAL_MS = 500;
-export const POLL_INTERVAL_SLOW_MS = 1000;
 export const POLL_INTERVAL_BACKGROUND_MS = 2000;
 
 // Timeouts
-export const DEFAULT_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
 export const MAX_POLL_TIME_MS = 5 * 60 * 1000; // 5 minutes
-export const FALLBACK_FAILOVER_TIMEOUT_MS = 15_000;
 
 // Subagent depth limits
 export const DEFAULT_MAX_SUBAGENT_DEPTH = 3;

+ 2 - 10
src/config/council-schema.test.ts

@@ -37,11 +37,7 @@ describe('CouncillorConfigSchema', () => {
 
     if (result.success) {
       // Deprecated fields are stripped but reported via _deprecated
-      expect(result.data._deprecated).toEqual([
-        'master',
-        'master_timeout',
-        'master_fallback',
-      ]);
+      expect(result.data._deprecated).toEqual(['master']);
       // Core fields still work normally
       expect(result.data.timeout).toBe(180000);
       expect(Object.keys(result.data.presets.default)).toEqual(['alpha']);
@@ -147,11 +143,7 @@ test('deprecated master with non-standard model ID still parses', () => {
   expect(result.success).toBe(true);
 
   if (result.success) {
-    expect(result.data._deprecated).toEqual([
-      'master',
-      'master_timeout',
-      'master_fallback',
-    ]);
+    expect(result.data._deprecated).toEqual(['master']);
     // Even non-standard model IDs are extracted as-is for backward compat
     expect(result.data._legacyMasterModel).toBe('claude-opus-4-6');
   }

+ 0 - 30
src/config/council-schema.ts

@@ -151,21 +151,11 @@ export const CouncilConfigSchema = z
       .unknown()
       .optional()
       .describe('DEPRECATED — ignored. Council agent synthesizes directly.'),
-    master_timeout: z
-      .unknown()
-      .optional()
-      .describe('DEPRECATED — ignored. Use "timeout" instead.'),
-    master_fallback: z
-      .unknown()
-      .optional()
-      .describe('DEPRECATED — ignored. No separate master session.'),
   })
   .transform((data) => {
     // Detect deprecated fields and attach warning for consumers
     const deprecated: string[] = [];
     if (data.master !== undefined) deprecated.push('master');
-    if (data.master_timeout !== undefined) deprecated.push('master_timeout');
-    if (data.master_fallback !== undefined) deprecated.push('master_fallback');
 
     // Backward compat: extract master.model so the council agent can use it
     // as a fallback when no explicit council entry exists in the active preset.
@@ -194,26 +184,6 @@ export type CouncillorExecutionMode = z.infer<
   typeof CouncillorExecutionModeSchema
 >;
 
-/**
- * A sensible default council configuration that users can copy into their
- * opencode.jsonc. Provides a 3-councillor preset using common models.
- *
- * Users should replace models with ones they have access to.
- *
- * ```jsonc
- * "council": DEFAULT_COUNCIL_CONFIG
- * ```
- */
-export const DEFAULT_COUNCIL_CONFIG: z.input<typeof CouncilConfigSchema> = {
-  presets: {
-    default: {
-      alpha: { model: 'openai/gpt-5.4-mini' },
-      beta: { model: 'openai/gpt-5.3-codex' },
-      gamma: { model: 'google/gemini-3-pro' },
-    },
-  },
-};
-
 /**
  * Result of a council session.
  */

+ 0 - 34
src/config/fallback-chains.ts

@@ -1,34 +0,0 @@
-export function normalizeFallbackChainsForPreset(
-  chains: Record<string, string[] | undefined>,
-  presetName: string | null | undefined,
-): Record<string, string[]> {
-  const normalized: Record<string, string[]> = {};
-
-  for (const [rawKey, chainModels] of Object.entries(chains)) {
-    if (!chainModels?.length) continue;
-
-    const separatorIndex = rawKey.indexOf(':');
-    const hasPresetScope = separatorIndex !== -1;
-    const scopedPreset = hasPresetScope ? rawKey.slice(0, separatorIndex) : '';
-    const agentName = hasPresetScope
-      ? rawKey.slice(separatorIndex + 1)
-      : rawKey;
-
-    if (!agentName) continue;
-    if (hasPresetScope && scopedPreset !== presetName) continue;
-
-    const existing = normalized[agentName] ?? [];
-    const seen = new Set(existing);
-    for (const chainModel of chainModels) {
-      if (seen.has(chainModel)) continue;
-      seen.add(chainModel);
-      existing.push(chainModel);
-    }
-
-    if (existing.length > 0) {
-      normalized[agentName] = existing;
-    }
-  }
-
-  return normalized;
-}

+ 5 - 119
src/config/loader.test.ts

@@ -51,36 +51,6 @@ describe('loadPluginConfig', () => {
     expect(config.agents?.oracle?.model).toBe('test/model');
   });
 
-  test('loads scoringEngineVersion flag when configured', () => {
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        scoringEngineVersion: 'v2-shadow',
-      }),
-    );
-
-    const config = loadPluginConfig(projectDir);
-    expect(config.scoringEngineVersion).toBe('v2-shadow');
-  });
-
-  test('loads balanceProviderUsage flag when configured', () => {
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        balanceProviderUsage: true,
-      }),
-    );
-
-    const config = loadPluginConfig(projectDir);
-    expect(config.balanceProviderUsage).toBe(true);
-  });
-
   test('loads autoUpdate flag when configured', () => {
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');
@@ -96,60 +66,6 @@ describe('loadPluginConfig', () => {
     expect(config.autoUpdate).toBe(false);
   });
 
-  test('loads manual plan structure when configured', () => {
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        manualPlan: {
-          orchestrator: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/kimi-k2.5',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-          oracle: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-          designer: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/kimi-k2.5',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-          explorer: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/kimi-k2.5',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-          librarian: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/kimi-k2.5',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-          fixer: {
-            primary: 'openai/gpt-5.5',
-            fallback1: 'anthropic/claude-opus-4-6',
-            fallback2: 'chutes/kimi-k2.5',
-            fallback3: 'opencode/gpt-5-nano',
-          },
-        },
-      }),
-    );
-
-    const config = loadPluginConfig(projectDir);
-    expect(config.manualPlan?.oracle?.fallback2).toBe(
-      'chutes/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE',
-    );
-  });
-
   test('ignores invalid config (schema violation or malformed JSON)', () => {
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');
@@ -637,47 +553,18 @@ describe('deepMerge behavior', () => {
     expect(config.agents?.oracle?.model).toBe('user/model');
   });
 
-  test('merges fallback timeout and chains from user and project', () => {
+  test('merges fallback timeout from user and project', () => {
     const userOpencodeDir = path.join(userConfigDir, 'opencode');
     fs.mkdirSync(userOpencodeDir, { recursive: true });
     fs.writeFileSync(
       path.join(userOpencodeDir, 'oh-my-opencode-slim.json'),
       JSON.stringify({
         fallback: {
-          timeoutMs: 15000,
-          chains: {
-            oracle: ['openai/gpt-5.5', 'opencode/glm-4.7-free'],
-          },
-        },
-      }),
-    );
-
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        fallback: {
-          chains: {
-            explorer: ['google/antigravity-gemini-3-flash'],
-          },
+          enabled: true,
         },
       }),
     );
 
-    const config = loadPluginConfig(projectDir);
-    expect(config.fallback?.timeoutMs).toBe(15000);
-    expect(config.fallback?.chains.oracle).toEqual([
-      'openai/gpt-5.5',
-      'opencode/glm-4.7-free',
-    ]);
-    expect(config.fallback?.chains.explorer).toEqual([
-      'google/antigravity-gemini-3-flash',
-    ]);
-  });
-
-  test('preserves fallback chains with additional agent keys', () => {
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');
     fs.mkdirSync(projectConfigDir, { recursive: true });
@@ -685,15 +572,14 @@ describe('deepMerge behavior', () => {
       path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
       JSON.stringify({
         fallback: {
-          chains: {
-            writing: ['openai/gpt-5.5'],
-          },
+          enabled: false,
         },
       }),
     );
 
     const config = loadPluginConfig(projectDir);
-    expect(config.fallback?.chains.writing).toEqual(['openai/gpt-5.5']);
+    // Fallback deepMerge: project value wins over user value
+    expect(config.fallback?.enabled).toBe(false);
   });
 });
 

+ 0 - 196
src/config/model-resolution.test.ts

@@ -1,6 +1,5 @@
 import { describe, expect, test } from 'bun:test';
 import type { ModelEntry } from '../config/schema';
-import { normalizeFallbackChainsForPreset } from './fallback-chains';
 
 /**
  * Test the model array resolution logic that runs in the config hook.
@@ -66,198 +65,3 @@ describe('model array resolution', () => {
     expect(result).toBeNull();
   });
 });
-
-/**
- * Tests for the fallback.chains merging logic that runs in the config hook.
- * Mirrors the effectiveArrays construction in src/index.ts.
- */
-describe('fallback.chains merging for foreground agents', () => {
-  /**
-   * Simulates the effectiveArrays construction + resolution from src/index.ts.
-   * Returns the resolved model string or null.
-   */
-  function resolveWithChains(opts: {
-    modelArray?: Array<{ id: string; variant?: string }>;
-    currentModel?: string;
-    chainModels?: string[];
-    preset?: string;
-    fallbackEnabled?: boolean;
-  }): string | null {
-    const {
-      modelArray,
-      currentModel,
-      chainModels,
-      preset,
-      fallbackEnabled = true,
-    } = opts;
-
-    // Build effectiveArrays (mirrors index.ts logic)
-    const effectiveArray: Array<{ id: string; variant?: string }> = modelArray
-      ? [...modelArray]
-      : [];
-
-    if (fallbackEnabled && chainModels && chainModels.length > 0) {
-      const normalized = normalizeFallbackChainsForPreset(
-        {
-          [currentModel ?? 'orchestrator']: chainModels,
-        },
-        preset,
-      );
-      const normalizedModels = Object.values(normalized)[0] ?? [];
-      if (effectiveArray.length === 0 && currentModel) {
-        effectiveArray.push({ id: currentModel });
-      }
-      const seen = new Set(effectiveArray.map((m) => m.id));
-      for (const chainModel of normalizedModels) {
-        if (!seen.has(chainModel)) {
-          seen.add(chainModel);
-          effectiveArray.push({ id: chainModel });
-        }
-      }
-    }
-
-    if (effectiveArray.length === 0) return null;
-
-    // Resolution: always use first model in effective array
-    return effectiveArray[0].id;
-  }
-
-  test('primary model wins regardless of provider config', () => {
-    const result = resolveWithChains({
-      currentModel: 'anthropic/claude-opus-4-5',
-      chainModels: ['openai/gpt-4o'],
-    });
-    expect(result).toBe('anthropic/claude-opus-4-5');
-  });
-
-  test('chain is ignored when fallback disabled', () => {
-    const result = resolveWithChains({
-      currentModel: 'anthropic/claude-opus-4-5',
-      chainModels: ['openai/gpt-4o'],
-      fallbackEnabled: false,
-    });
-    // chain not applied; no effectiveArray entry → falls through to null (no _modelArray either)
-    expect(result).toBeNull();
-  });
-
-  test('_modelArray entries take precedence and chain appends after', () => {
-    const result = resolveWithChains({
-      modelArray: [
-        { id: 'anthropic/claude-opus-4-5' },
-        { id: 'anthropic/claude-sonnet-4-5' },
-      ],
-      chainModels: ['openai/gpt-4o'],
-    });
-    // First entry in _modelArray wins; chain only used for runtime failover
-    expect(result).toBe('anthropic/claude-opus-4-5');
-  });
-
-  test('duplicate model ids across array and chain are deduplicated', () => {
-    const result = resolveWithChains({
-      modelArray: [
-        { id: 'anthropic/claude-opus-4-5' },
-        { id: 'openai/gpt-4o' },
-      ],
-      chainModels: ['openai/gpt-4o', 'google/gemini-pro'],
-    });
-    expect(result).toBe('anthropic/claude-opus-4-5');
-  });
-
-  test('no currentModel and no _modelArray with chain still resolves', () => {
-    const result = resolveWithChains({
-      chainModels: ['openai/gpt-4o', 'anthropic/claude-sonnet-4-5'],
-    });
-    expect(result).toBe('openai/gpt-4o');
-  });
-
-  test('built-in provider not skipped when other providers are configured', () => {
-    // Regression test: github-copilot is auto-loaded by opencode and doesn't
-    // need an entry in opencodeConfig.provider. The resolver must not skip
-    // it in favor of a configured provider later in the chain.
-    const result = resolveWithChains({
-      currentModel: 'github-copilot/claude-opus-4.6',
-      chainModels: [
-        'github-copilot/gemini-3.1-pro-preview',
-        'zai-coding-plan/glm-5',
-      ],
-    });
-    expect(result).toBe('github-copilot/claude-opus-4.6');
-  });
-
-  test('normalizes scoped fallback chains for the active preset', () => {
-    const result = normalizeFallbackChainsForPreset(
-      {
-        'gpt-plus-max:orchestrator': [
-          'openai/o3',
-          'anthropic/claude-sonnet-4-6',
-        ],
-        'gpt-plus-max:oracle': ['anthropic/claude-opus-4-5'],
-      },
-      'gpt-plus-max',
-    );
-
-    expect(result).toEqual({
-      orchestrator: ['openai/o3', 'anthropic/claude-sonnet-4-6'],
-      oracle: ['anthropic/claude-opus-4-5'],
-    });
-  });
-
-  test('ignores scoped fallback chains for other presets', () => {
-    const result = normalizeFallbackChainsForPreset(
-      {
-        'gpt-plus-max:orchestrator': ['openai/o3'],
-        orchestrator: ['anthropic/claude-sonnet-4-6'],
-      },
-      'other-preset',
-    );
-
-    expect(result).toEqual({
-      orchestrator: ['anthropic/claude-sonnet-4-6'],
-    });
-  });
-
-  test('does not emit scoped keys in normalized output', () => {
-    const result = normalizeFallbackChainsForPreset(
-      {
-        'gpt-plus-max:orchestrator': ['openai/o3'],
-      },
-      'gpt-plus-max',
-    );
-
-    expect(Object.keys(result).some((key) => key.includes(':'))).toBe(false);
-  });
-
-  test('ignores empty fallback chain agent names', () => {
-    const result = normalizeFallbackChainsForPreset(
-      {
-        '': ['openai/o3'],
-        'gpt-plus-max:': ['anthropic/claude-sonnet-4-6'],
-        orchestrator: ['ustc-deepseek/deepseek-v4-pro'],
-      },
-      'gpt-plus-max',
-    );
-
-    expect(result).toEqual({
-      orchestrator: ['ustc-deepseek/deepseek-v4-pro'],
-    });
-    expect(result).not.toHaveProperty('');
-  });
-
-  test('runtime preset takes precedence over config preset for scoped fallback chains', () => {
-    const configPreset = 'cheap';
-    const runtimePreset = 'powerful';
-    const activePreset = runtimePreset ?? configPreset ?? null;
-
-    const result = normalizeFallbackChainsForPreset(
-      {
-        'cheap:orchestrator': ['cheap/model'],
-        'powerful:orchestrator': ['powerful/model'],
-      },
-      activePreset,
-    );
-
-    expect(result).toEqual({
-      orchestrator: ['powerful/model'],
-    });
-  });
-});

+ 0 - 20
src/config/runtime-preset.test.ts

@@ -2,7 +2,6 @@ import { describe, expect, test } from 'bun:test';
 import {
   getActiveRuntimePreset,
   getPreviousRuntimePreset,
-  rollbackRuntimePreset,
   setActiveRuntimePreset,
   setActiveRuntimePresetWithPrevious,
 } from './runtime-preset';
@@ -39,23 +38,4 @@ describe('runtime-preset', () => {
     expect(getPreviousRuntimePreset()).toBe('old');
     setActiveRuntimePreset(null);
   });
-
-  test('rollbackRuntimePreset restores active and clears previous', () => {
-    setActiveRuntimePreset(null);
-    setActiveRuntimePreset('old');
-    setActiveRuntimePresetWithPrevious('new');
-    rollbackRuntimePreset('old');
-    expect(getActiveRuntimePreset()).toBe('old');
-    expect(getPreviousRuntimePreset()).toBeNull();
-    setActiveRuntimePreset(null);
-  });
-
-  test('rollbackRuntimePreset with null clears active and previous', () => {
-    setActiveRuntimePreset(null);
-    setActiveRuntimePresetWithPrevious('new');
-    rollbackRuntimePreset(null);
-    expect(getActiveRuntimePreset()).toBeNull();
-    expect(getPreviousRuntimePreset()).toBeNull();
-    setActiveRuntimePreset(null);
-  });
 });

+ 0 - 5
src/config/runtime-preset.ts

@@ -30,8 +30,3 @@ export function setActiveRuntimePresetWithPrevious(name: string | null): void {
   previousRuntimePreset = activeRuntimePreset;
   activeRuntimePreset = name;
 }
-
-export function rollbackRuntimePreset(previous: string | null): void {
-  activeRuntimePreset = previous;
-  previousRuntimePreset = null;
-}

+ 0 - 83
src/config/schema.ts

@@ -2,83 +2,6 @@ import { z } from 'zod';
 import { AGENT_ALIASES, ALL_AGENT_NAMES } from './constants';
 import { CouncilConfigSchema } from './council-schema';
 
-const FALLBACK_AGENT_NAMES = [
-  'orchestrator',
-  'oracle',
-  'designer',
-  'explorer',
-  'librarian',
-  'fixer',
-] as const;
-
-const MANUAL_AGENT_NAMES = [
-  'orchestrator',
-  'oracle',
-  'designer',
-  'explorer',
-  'librarian',
-  'fixer',
-] as const;
-
-export const ProviderModelIdSchema = z
-  .string()
-  .regex(
-    /^[^/\s]+\/[^\s]+$/,
-    'Expected provider/model format (provider/.../model)',
-  );
-
-export const ManualAgentPlanSchema = z
-  .object({
-    primary: ProviderModelIdSchema,
-    fallback1: ProviderModelIdSchema,
-    fallback2: ProviderModelIdSchema,
-    fallback3: ProviderModelIdSchema,
-  })
-  .superRefine((value, ctx) => {
-    const unique = new Set([
-      value.primary,
-      value.fallback1,
-      value.fallback2,
-      value.fallback3,
-    ]);
-    if (unique.size !== 4) {
-      ctx.addIssue({
-        code: z.ZodIssueCode.custom,
-        message: 'primary and fallbacks must be unique per agent',
-      });
-    }
-  });
-
-export const ManualPlanSchema = z
-  .object({
-    orchestrator: ManualAgentPlanSchema,
-    oracle: ManualAgentPlanSchema,
-    designer: ManualAgentPlanSchema,
-    explorer: ManualAgentPlanSchema,
-    librarian: ManualAgentPlanSchema,
-    fixer: ManualAgentPlanSchema,
-  })
-  .strict();
-
-export type ManualAgentName = (typeof MANUAL_AGENT_NAMES)[number];
-export type ManualAgentPlan = z.infer<typeof ManualAgentPlanSchema>;
-export type ManualPlan = z.infer<typeof ManualPlanSchema>;
-
-const AgentModelChainSchema = z.array(z.string()).min(1);
-
-const FallbackChainsSchema = z
-  .object({
-    orchestrator: AgentModelChainSchema.optional(),
-    oracle: AgentModelChainSchema.optional(),
-    designer: AgentModelChainSchema.optional(),
-    explorer: AgentModelChainSchema.optional(),
-    librarian: AgentModelChainSchema.optional(),
-    fixer: AgentModelChainSchema.optional(),
-  })
-  .catchall(AgentModelChainSchema);
-
-export type FallbackAgentName = (typeof FALLBACK_AGENT_NAMES)[number];
-
 // Agent override configuration (distinct from SDK's AgentConfig)
 export const AgentOverrideConfigSchema = z
   .object({
@@ -196,9 +119,6 @@ export type BackgroundJobsConfig = z.infer<typeof BackgroundJobsConfigSchema>;
 
 export const FailoverConfigSchema = z.object({
   enabled: z.boolean().default(true),
-  timeoutMs: z.number().min(0).default(15000),
-  retryDelayMs: z.number().min(0).default(500),
-  chains: FallbackChainsSchema.default({}),
   retry_on_empty: z
     .boolean()
     .default(true)
@@ -256,15 +176,12 @@ export const PluginConfigSchema = z
   .object({
     preset: z.string().optional(),
     setDefaultAgent: z.boolean().optional(),
-    scoringEngineVersion: z.enum(['v1', 'v2-shadow', 'v2']).optional(),
-    balanceProviderUsage: z.boolean().optional(),
     autoUpdate: z
       .boolean()
       .optional()
       .describe(
         'Disable automatic installation of plugin updates when false. Defaults to true.',
       ),
-    manualPlan: ManualPlanSchema.optional(),
     presets: z.record(z.string(), PresetSchema).optional(),
     agents: z.record(z.string(), AgentOverrideConfigSchema).optional(),
     disabled_agents: z

+ 0 - 9
src/hooks/apply-patch/patch.ts

@@ -1,9 +0,0 @@
-// Compatibility shim for local deep imports; keep only the stable runtime
-// surface here. `applyPreparedChanges()` remains in operations.ts as an
-// internal best-effort helper for local tests/helpers.
-export { parsePatch } from './codec';
-export {
-  preparePatchChanges,
-  rewritePatch,
-  rewritePatchText,
-} from './operations';

+ 0 - 41
src/hooks/delegate-task-retry/guidance.ts

@@ -1,41 +0,0 @@
-import { DELEGATE_TASK_ERROR_PATTERNS, type DetectedError } from './patterns';
-
-function extractAvailableList(output: string): string | null {
-  const match = output.match(/Allowed agents:\s*(.+)$/m);
-  if (match) return match[1].trim();
-
-  const available = output.match(/Available[^:]*:\s*(.+)$/m);
-  if (available) return available[1].trim();
-
-  return null;
-}
-
-export function buildRetryGuidance(errorInfo: DetectedError): string {
-  const pattern = DELEGATE_TASK_ERROR_PATTERNS.find(
-    (p) => p.errorType === errorInfo.errorType,
-  );
-
-  if (!pattern) {
-    return '\n[delegate-task retry] Fix parameters and retry with corrected arguments.';
-  }
-
-  const available = extractAvailableList(errorInfo.originalOutput);
-
-  const lines = [
-    '',
-    '[delegate-task retry suggestion]',
-    `Error type: ${errorInfo.errorType}`,
-    `Fix: ${pattern.fixHint}`,
-  ];
-
-  if (available) {
-    lines.push(`Available: ${available}`);
-  }
-
-  lines.push(
-    'Retry now with corrected parameters. Example:',
-    'task(description="...", prompt="...", category="unspecified-low", run_in_background=false, load_skills=[])',
-  );
-
-  return lines.join('\n');
-}

+ 45 - 2
src/hooks/delegate-task-retry/hook.ts

@@ -1,6 +1,49 @@
 import type { PluginInput } from '@opencode-ai/plugin';
-import { buildRetryGuidance } from './guidance';
-import { detectDelegateTaskError } from './patterns';
+import {
+  DELEGATE_TASK_ERROR_PATTERNS,
+  type DetectedError,
+  detectDelegateTaskError,
+} from './patterns';
+
+function extractAvailableList(output: string): string | null {
+  const match = output.match(/Allowed agents:\s*(.+)$/m);
+  if (match) return match[1].trim();
+
+  const available = output.match(/Available[^:]*:\s*(.+)$/m);
+  if (available) return available[1].trim();
+
+  return null;
+}
+
+function buildRetryGuidance(errorInfo: DetectedError): string {
+  const pattern = DELEGATE_TASK_ERROR_PATTERNS.find(
+    (p) => p.errorType === errorInfo.errorType,
+  );
+
+  if (!pattern) {
+    return '\n[delegate-task retry] Fix parameters and retry with corrected arguments.';
+  }
+
+  const available = extractAvailableList(errorInfo.originalOutput);
+
+  const lines = [
+    '',
+    '[delegate-task retry suggestion]',
+    `Error type: ${errorInfo.errorType}`,
+    `Fix: ${pattern.fixHint}`,
+  ];
+
+  if (available) {
+    lines.push(`Available: ${available}`);
+  }
+
+  lines.push(
+    'Retry now with corrected parameters. Example:',
+    'task(description="...", prompt="...", category="unspecified-low", run_in_background=false, load_skills=[])',
+  );
+
+  return lines.join('\n');
+}
 
 export function createDelegateTaskRetryHook(_ctx: PluginInput) {
   return {

+ 0 - 7
src/hooks/delegate-task-retry/index.ts

@@ -1,7 +0,0 @@
-export { buildRetryGuidance } from './guidance';
-export { createDelegateTaskRetryHook } from './hook';
-export type { DelegateTaskErrorPattern, DetectedError } from './patterns';
-export {
-  DELEGATE_TASK_ERROR_PATTERNS,
-  detectDelegateTaskError,
-} from './patterns';

+ 3 - 9
src/hooks/foreground-fallback/index.ts

@@ -59,13 +59,7 @@ export function isRateLimitError(error: unknown): boolean {
 // Helpers
 // ---------------------------------------------------------------------------
 
-function parseModel(
-  model: string,
-): { providerID: string; modelID: string } | null {
-  const slash = model.indexOf('/');
-  if (slash <= 0 || slash >= model.length - 1) return null;
-  return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
-}
+import { parseModelReference } from '../../utils/session';
 
 /** Prevent re-triggering within this window for the same session. */
 const DEDUP_WINDOW_MS = 5_000;
@@ -79,7 +73,7 @@ const REPROMPT_DELAY_MS = 500;
  * Manages runtime model fallback for foreground agent sessions.
  *
  * Constructed at plugin init with the ordered fallback chains for each agent
- * (built from _modelArray entries merged with fallback.chains config).
+ * (built from _modelArray entries in agents.<name>.model).
  */
 export class ForegroundFallbackManager {
   /** sessionID → last observed model string ("providerID/modelID") */
@@ -255,7 +249,7 @@ export class ForegroundFallbackManager {
       }
       tried.add(nextModel);
 
-      const ref = parseModel(nextModel);
+      const ref = parseModelReference(nextModel);
       if (!ref) {
         log('[foreground-fallback] invalid model format', {
           sessionID,

+ 2 - 2
src/hooks/index.ts

@@ -3,14 +3,14 @@ export type { AutoUpdateCheckerOptions } from './auto-update-checker';
 export { createAutoUpdateCheckerHook } from './auto-update-checker';
 export { createChatHeadersHook } from './chat-headers';
 export { createDeepworkCommandHook } from './deepwork';
-export { createDelegateTaskRetryHook } from './delegate-task-retry';
+export { createDelegateTaskRetryHook } from './delegate-task-retry/hook';
 export { createFilterAvailableSkillsHook } from './filter-available-skills';
 export {
   ForegroundFallbackManager,
   isRateLimitError,
 } from './foreground-fallback';
 export { processImageAttachments } from './image-hook';
-export { createJsonErrorRecoveryHook } from './json-error-recovery';
+export { createJsonErrorRecoveryHook } from './json-error-recovery/hook';
 export { createPhaseReminderHook } from './phase-reminder';
 export { createPostFileToolNudgeHook } from './post-file-tool-nudge';
 export { createTaskSessionManagerHook } from './task-session-manager';

+ 1 - 1
src/hooks/json-error-recovery/index.test.ts

@@ -5,7 +5,7 @@ import {
   JSON_ERROR_PATTERNS,
   JSON_ERROR_REMINDER,
   JSON_ERROR_TOOL_EXCLUDE_LIST,
-} from './index';
+} from './hook';
 
 describe('json-error-recovery hook', () => {
   let hook: ReturnType<typeof createJsonErrorRecoveryHook>;

+ 0 - 6
src/hooks/json-error-recovery/index.ts

@@ -1,6 +0,0 @@
-export {
-  createJsonErrorRecoveryHook,
-  JSON_ERROR_PATTERNS,
-  JSON_ERROR_REMINDER,
-  JSON_ERROR_TOOL_EXCLUDE_LIST,
-} from './hook';

+ 2 - 4
src/hooks/post-file-tool-nudge/index.ts

@@ -5,8 +5,6 @@
 
 import { PHASE_REMINDER_TEXT } from '../../config/constants';
 
-const POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
-
 interface ToolExecuteAfterInput {
   tool: string;
   sessionID?: string;
@@ -31,7 +29,7 @@ export function createPostFileToolNudgeHook(
       return;
     }
 
-    if (output.output.includes(POST_FILE_TOOL_NUDGE)) {
+    if (output.output.includes(PHASE_REMINDER_TEXT)) {
       return;
     }
 
@@ -39,7 +37,7 @@ export function createPostFileToolNudgeHook(
       output.output,
       '',
       '<internal_reminder>',
-      POST_FILE_TOOL_NUDGE,
+      PHASE_REMINDER_TEXT,
       '</internal_reminder>',
     ].join('\n');
   }

+ 1 - 3
src/hooks/task-session-manager/index.ts

@@ -126,9 +126,7 @@ function isAgentName(value: unknown): value is AgentName {
   return typeof value === 'string' && AGENT_NAME_SET.has(value as AgentName);
 }
 
-function isObjectRecord(value: unknown): value is Record<string, unknown> {
-  return typeof value === 'object' && value !== null;
-}
+import { isRecord as isObjectRecord } from '../../utils/guards';
 
 function extractPath(output: string): string | undefined {
   return /<path>([^<]+)<\/path>/.exec(output)?.[1];

+ 4 - 66
src/index.ts

@@ -10,7 +10,6 @@ import {
 } from './config';
 import { parseList } from './config/agent-mcps';
 import { AGENT_ALIASES } from './config/constants';
-import { normalizeFallbackChainsForPreset } from './config/fallback-chains';
 import {
   getActiveRuntimePreset,
   getPreviousRuntimePreset,
@@ -191,34 +190,14 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
     }
     // Build runtime fallback chains for all foreground agents. Each chain
     // is an ordered list of model strings to try when the current model is
-    // rate-limited. Seeds from _modelArray entries (when the user
-    // configures model as an array), then appends fallback.chains entries.
+    // rate-limited. Populated from _modelArray entries (when the user
+    // configures model as an array in agents.<name>.model).
     runtimeChains = {} as Record<string, string[]>;
     for (const agentDef of agentDefs) {
       if (agentDef._modelArray?.length) {
         runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
       }
     }
-    const activePresetForFallback =
-      getActiveRuntimePreset() ?? config.preset ?? null;
-
-    if (config.fallback?.enabled !== false) {
-      const chains = normalizeFallbackChainsForPreset(
-        (config.fallback?.chains as Record<string, string[] | undefined>) ?? {},
-        activePresetForFallback,
-      );
-      for (const [agentName, chainModels] of Object.entries(chains)) {
-        const existing = runtimeChains[agentName] ?? [];
-        const seen = new Set(existing);
-        for (const m of chainModels) {
-          if (!seen.has(m)) {
-            seen.add(m);
-            existing.push(m);
-          }
-        }
-        runtimeChains[agentName] = existing;
-      }
-    }
 
     // Parse multiplexer config with defaults
     multiplexerConfig = {
@@ -445,27 +424,12 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       }
       const configAgent = opencodeConfig.agent as Record<string, unknown>;
 
-      // Model resolution for foreground agents: combine _modelArray
-      // entries with fallback.chains config, then pick the first model in
-      // the effective array for startup-time selection.
+      // Model resolution for foreground agents: use _modelArray entries
+      // to pick the first model for startup-time selection.
       //
       // Runtime failover on API errors (e.g. rate limits
       // mid-conversation) is handled separately by
       // ForegroundFallbackManager via the event hook.
-      const activePresetForFallback =
-        getActiveRuntimePreset() ?? config.preset ?? null;
-      const fallbackChainsEnabled = config.fallback?.enabled !== false;
-      const fallbackChains = fallbackChainsEnabled
-        ? normalizeFallbackChainsForPreset(
-            (config.fallback?.chains as Record<string, string[] | undefined>) ??
-              {},
-            activePresetForFallback,
-          )
-        : {};
-
-      // Build effective model arrays: seed from _modelArray, then append
-      // fallback.chains entries so the resolver considers the full chain
-      // when picking the best available provider at startup.
       const effectiveArrays: Record<
         string,
         Array<{ id: string; variant?: string }>
@@ -475,32 +439,6 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         effectiveArrays[agentName] = [...models];
       }
 
-      for (const [agentName, chainModels] of Object.entries(fallbackChains)) {
-        if (!chainModels || chainModels.length === 0) continue;
-
-        if (!effectiveArrays[agentName]) {
-          // Agent has no _modelArray — seed from its current string model
-          // so the fallback chain appends after it rather than replacing
-          // it.
-          const entry = configAgent[agentName] as
-            | Record<string, unknown>
-            | undefined;
-          const currentModel =
-            typeof entry?.model === 'string' ? entry.model : undefined;
-          effectiveArrays[agentName] = currentModel
-            ? [{ id: currentModel }]
-            : [];
-        }
-
-        const seen = new Set(effectiveArrays[agentName].map((m) => m.id));
-        for (const chainModel of chainModels) {
-          if (!seen.has(chainModel)) {
-            seen.add(chainModel);
-            effectiveArrays[agentName].push({ id: chainModel });
-          }
-        }
-      }
-
       if (Object.keys(effectiveArrays).length > 0) {
         for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
           if (modelArray.length === 0) continue;

+ 1 - 3
src/tools/cancel-task.ts

@@ -460,9 +460,7 @@ function delay(ms: number): Promise<void> {
   return new Promise((resolve) => setTimeout(resolve, ms));
 }
 
-function isObjectRecord(value: unknown): value is Record<string, unknown> {
-  return typeof value === 'object' && value !== null;
-}
+import { isRecord as isObjectRecord } from '../utils/guards';
 
 function isSessionID(value: string): boolean {
   return /^ses_[\w-]+$/.test(value);

+ 1 - 188
src/utils/agent-variant.test.ts

@@ -1,12 +1,6 @@
 import { describe, expect, test } from 'bun:test';
 import type { PluginConfig } from '../config';
-import {
-  applyAgentVariant,
-  normalizeAgentName,
-  resolveAgentVariant,
-  resolveRuntimeAgentName,
-  rewriteDisplayNameMentions,
-} from './agent-variant';
+import { normalizeAgentName, resolveRuntimeAgentName } from './agent-variant';
 
 describe('normalizeAgentName', () => {
   test('returns name unchanged if no @ prefix', () => {
@@ -30,89 +24,6 @@ describe('normalizeAgentName', () => {
   });
 });
 
-describe('resolveAgentVariant', () => {
-  test('returns undefined when config is undefined', () => {
-    expect(resolveAgentVariant(undefined, 'oracle')).toBeUndefined();
-  });
-
-  test('returns undefined when agents is undefined', () => {
-    const config = {} as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
-  });
-
-  test('returns undefined when agent has no variant', () => {
-    const config = {
-      agents: {
-        oracle: { model: 'gpt-4' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
-  });
-
-  test('returns variant when configured', () => {
-    const config = {
-      agents: {
-        oracle: { variant: 'high' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBe('high');
-  });
-
-  test('normalizes agent name with @ prefix', () => {
-    const config = {
-      agents: {
-        oracle: { variant: 'low' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, '@oracle')).toBe('low');
-  });
-
-  test('returns undefined for empty string variant', () => {
-    const config = {
-      agents: {
-        oracle: { variant: '' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
-  });
-
-  test('returns undefined for whitespace-only variant', () => {
-    const config = {
-      agents: {
-        oracle: { variant: '   ' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
-  });
-
-  test('trims variant whitespace', () => {
-    const config = {
-      agents: {
-        oracle: { variant: '  medium  ' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBe('medium');
-  });
-
-  test('returns undefined for non-string variant', () => {
-    const config = {
-      agents: {
-        oracle: { variant: 123 as unknown as string },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
-  });
-
-  test('resolves displayName alias to internal agent for variant lookup', () => {
-    const config = {
-      agents: {
-        oracle: { displayName: 'advisor', variant: 'high' },
-      },
-    } as PluginConfig;
-    expect(resolveAgentVariant(config, '@advisor')).toBe('high');
-  });
-});
-
 describe('resolveRuntimeAgentName', () => {
   test('keeps internal agent names unchanged', () => {
     const config = {
@@ -164,101 +75,3 @@ describe('resolveRuntimeAgentName', () => {
     expect(resolveRuntimeAgentName(config, '  @unknown  ')).toBe('unknown');
   });
 });
-
-describe('rewriteDisplayNameMentions', () => {
-  test('rewrites displayName mentions to internal names for direct invocation', () => {
-    const config = {
-      agents: {
-        oracle: { displayName: 'advisor' },
-      },
-    } as PluginConfig;
-
-    expect(rewriteDisplayNameMentions(config, 'ask @advisor about this')).toBe(
-      'ask @oracle about this',
-    );
-  });
-
-  test('keeps internal mentions working while rewriting aliases', () => {
-    const config = {
-      agents: {
-        oracle: { displayName: 'advisor' },
-      },
-    } as PluginConfig;
-
-    expect(
-      rewriteDisplayNameMentions(config, 'compare @advisor with @oracle'),
-    ).toBe('compare @oracle with @oracle');
-  });
-
-  test('does not rewrite embedded text such as email addresses', () => {
-    const config = {
-      agents: {
-        oracle: { displayName: 'advisor' },
-      },
-    } as PluginConfig;
-
-    expect(
-      rewriteDisplayNameMentions(
-        config,
-        'email foo@advisor.com and ask @advisor directly',
-      ),
-    ).toBe('email foo@advisor.com and ask @oracle directly');
-  });
-
-  test('resolves custom agents by displayName for variant/runtime lookups', () => {
-    const config = {
-      agents: {
-        'custom-reviewer': {
-          displayName: 'reviewer',
-          variant: 'high',
-          model: 'openai/gpt-5.5',
-        },
-      },
-    } as PluginConfig;
-
-    expect(resolveRuntimeAgentName(config, '@reviewer')).toBe(
-      'custom-reviewer',
-    );
-    expect(
-      rewriteDisplayNameMentions(config, 'ask @reviewer for details'),
-    ).toBe('ask @custom-reviewer for details');
-    expect(resolveAgentVariant(config, '@reviewer')).toBe('high');
-  });
-});
-
-describe('applyAgentVariant', () => {
-  test('returns body unchanged when variant is undefined', () => {
-    const body = { agent: 'oracle', parts: [] };
-    const result = applyAgentVariant(undefined, body);
-    expect(result).toEqual(body);
-    expect(result).toBe(body); // Same reference
-  });
-
-  test('returns body unchanged when body already has variant', () => {
-    const body = { agent: 'oracle', variant: 'medium', parts: [] };
-    const result = applyAgentVariant('high', body);
-    expect(result.variant).toBe('medium');
-    expect(result).toBe(body); // Same reference
-  });
-
-  test('applies variant to body without variant', () => {
-    const body = { agent: 'oracle', parts: [] };
-    const result = applyAgentVariant('high', body);
-    expect(result.variant).toBe('high');
-    expect(result.agent).toBe('oracle');
-    expect(result).not.toBe(body); // New object
-  });
-
-  test('preserves all existing body properties', () => {
-    const body = {
-      agent: 'oracle',
-      parts: [{ type: 'text' as const, text: 'hello' }],
-      tools: { task: false },
-    };
-    const result = applyAgentVariant('low', body);
-    expect(result.agent).toBe('oracle');
-    expect(result.parts).toEqual([{ type: 'text', text: 'hello' }]);
-    expect(result.tools).toEqual({ task: false });
-    expect(result.variant).toBe('low');
-  });
-});

+ 0 - 76
src/utils/agent-variant.ts

@@ -4,7 +4,6 @@ import {
   getCustomAgentNames,
   type PluginConfig,
 } from '../config';
-import { log } from './logger';
 
 /**
  * Normalizes an agent name by trimming whitespace and removing the optional @ prefix.
@@ -29,42 +28,6 @@ function getRuntimeAgentNames(config?: PluginConfig): string[] {
   return [...unique];
 }
 
-/**
- * Resolves the variant configuration for a specific agent.
- *
- * Looks up the agent's variant in the plugin configuration. Returns undefined if:
- * - No config is provided
- * - The agent has no variant configured
- * - The variant is not a string
- * - The variant is empty or whitespace-only
- *
- * @param config - The plugin configuration object
- * @param agentName - The name of the agent (with or without @ prefix)
- * @returns The trimmed variant string, or undefined if no valid variant is found
- *
- * @example
- * resolveAgentVariant(config, "@oracle") // returns "high" if configured
- */
-export function resolveAgentVariant(
-  config: PluginConfig | undefined,
-  agentName: string,
-): string | undefined {
-  const normalized = resolveRuntimeAgentName(config, agentName);
-  const rawVariant = getAgentOverride(config, normalized)?.variant;
-
-  if (typeof rawVariant !== 'string') {
-    return undefined;
-  }
-
-  const trimmed = rawVariant.trim();
-  if (trimmed.length === 0) {
-    return undefined;
-  }
-
-  log(`[variant] resolved variant="${trimmed}" for agent "${normalized}"`);
-  return trimmed;
-}
-
 /**
  * Resolve a runtime-provided agent name to an internal agent name.
  *
@@ -151,42 +114,3 @@ export function createDisplayNameMentionRewriter(
     return rewritten;
   };
 }
-
-/**
- * Rewrites user-facing display-name mentions (e.g. @advisor) into internal
- * agent mentions (e.g. @oracle) for runtime routing.
- */
-export function rewriteDisplayNameMentions(
-  config: PluginConfig | undefined,
-  text: string,
-): string {
-  return createDisplayNameMentionRewriter(config)(text);
-}
-
-/**
- * Applies a variant to a request body if the body doesn't already have one.
- *
- * This function will NOT override an existing variant in the body. If no variant
- * is provided or the body already has a variant, the original body is returned.
- *
- * @template T - The type of the body object, must have an optional variant property
- * @param variant - The variant string to apply (or undefined)
- * @param body - The request body object
- * @returns The body with the variant applied (new object) or the original body unchanged
- *
- * @example
- * applyAgentVariant("high", { agent: "oracle" }) // returns { agent: "oracle", variant: "high" }
- * applyAgentVariant("high", { agent: "oracle", variant: "low" }) // returns original body with variant: "low"
- */
-export function applyAgentVariant<T extends { variant?: string }>(
-  variant: string | undefined,
-  body: T,
-): T {
-  if (!variant) {
-    return body;
-  }
-  if (body.variant) {
-    return body;
-  }
-  return { ...body, variant };
-}

+ 0 - 2
src/utils/compat.ts

@@ -2,8 +2,6 @@ import type { ChildProcess } from 'node:child_process';
 import { spawn as nodeSpawn } from 'node:child_process';
 import { writeFile as fsWriteFile } from 'node:fs/promises';
 
-export const isBun = typeof globalThis.Bun !== 'undefined';
-
 export interface CrossSpawnResult {
   proc: ChildProcess;
   /** Collects all stdout into a string */

+ 0 - 12
src/utils/env.ts

@@ -1,12 +0,0 @@
-export function getEnv(name: string): string | undefined {
-  const bunValue = (globalThis as { Bun?: { env?: Record<string, string> } })
-    .Bun?.env?.[name];
-  if (typeof bunValue === 'string' && bunValue.length > 0) return bunValue;
-
-  const processValue = (
-    globalThis as { process?: { env?: Record<string, string | undefined> } }
-  ).process?.env?.[name];
-  return typeof processValue === 'string' && processValue.length > 0
-    ? processValue
-    : undefined;
-}

+ 6 - 0
src/utils/guards.ts

@@ -0,0 +1,6 @@
+/**
+ * Shared type guard: checks if a value is a non-null object.
+ */
+export function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null;
+}

+ 1 - 2
src/utils/index.ts

@@ -1,8 +1,7 @@
 export * from './agent-variant';
 export * from './background-job-board';
-export * from './env';
 export * from './internal-initiator';
-export { getLogDir, initLogger, log, resetLogger } from './logger';
+export { getLogDir, initLogger, log } from './logger';
 export * from './polling';
 export * from './session';
 export * from './task';

+ 2 - 4
src/utils/internal-initiator.ts

@@ -1,10 +1,8 @@
+import { isRecord } from './guards';
+
 export const SLIM_INTERNAL_INITIATOR_MARKER =
   '<!-- SLIM_INTERNAL_INITIATOR -->';
 
-function isRecord(value: unknown): value is Record<string, unknown> {
-  return typeof value === 'object' && value !== null;
-}
-
 export function createInternalAgentTextPart(text: string): {
   type: 'text';
   text: string;

+ 2 - 3
src/utils/logger.ts

@@ -76,6 +76,8 @@ export function initLogger(sessionId: string): void {
   cleanupOldLogs(dir);
 }
 
+export { getLogDir };
+
 /** @internal Reset logger state for testing */
 export function resetLogger(): void {
   logFile = null;
@@ -86,9 +88,6 @@ export function resetLogger(): void {
 export async function flushLoggerForTesting(): Promise<void> {
   await writeChain;
 }
-
-export { getLogDir };
-
 export function log(message: string, data?: unknown): void {
   const target = logFile;
   if (!target) return; // Uninitialized — silently no-op

+ 0 - 26
src/utils/task.ts

@@ -17,18 +17,6 @@ export interface TaskStatusOutput {
   result?: string;
 }
 
-export type TaskStatusClassification =
-  | 'running'
-  | 'terminal'
-  | 'timeout'
-  | 'transient_process_error'
-  | 'unknown_error';
-
-const TRANSIENT_PROCESS_ERROR_TEXT = new Set([
-  'Task is not running in this process and has no final output.',
-  'Task is not running in this process and has not produced output.',
-]);
-
 export function parseTaskIdFromTaskOutput(output: string): string | undefined {
   const xmlMatch = /<task\s+[^>]*\bid=["']([^"']+)["'][^>]*>/i.exec(output);
   if (xmlMatch) return xmlMatch[1];
@@ -80,20 +68,6 @@ export function parseTaskStatusOutput(
   };
 }
 
-export function classifyTaskStatusOutput(
-  status: TaskStatusOutput,
-): TaskStatusClassification {
-  if (status.timedOut) return 'timeout';
-  if (status.state === 'running') return 'running';
-  if (status.state === 'completed' || status.state === 'cancelled') {
-    return 'terminal';
-  }
-  if (TRANSIENT_PROCESS_ERROR_TEXT.has(status.result ?? '')) {
-    return 'transient_process_error';
-  }
-  return 'unknown_error';
-}
-
 export function parseTaskStateFromOutput(
   output: string,
 ): TaskOutputState | undefined {