diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a16d45d54..61e63340e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added +- Added `plan` model role for specifying the model used by the plan agent +- Added `--plan` CLI flag and `OMP_PLAN_MODEL` environment variable for ephemeral plan model override +- Added plan model selection in model selector UI with PLAN badge + ## [8.5.0] - 2026-01-27 ### Added diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 61ab95a58..62b0c2c76 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -263,7 +263,7 @@ The agent reads, writes, and edits files, and executes commands via bash. | Ctrl+D | Exit (when editor is empty) | | Ctrl+Z | Suspend to background (use `fg` in shell to resume) | | Shift+Tab | Cycle thinking level | -| Ctrl+P / Shift+Ctrl+P | Cycle role models (slow/default/smol) | +| Ctrl+P / Shift+Ctrl+P | Cycle role models (slow/default/smol/plan) | | Ctrl+L | Open model selector | | Ctrl+O | Toggle tool output expansion | | Ctrl+T | Toggle todo list expansion | @@ -564,7 +564,7 @@ Global `~/.omp/agent/settings.json` stores persistent preferences: | Setting | Description | Default | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------- | | `theme` | Color theme name | auto-detected | -| `modelRoles` | Model assignments by role (e.g., `{"default": "anthropic/claude-sonnet-4-20250514", "slow": "...", "smol": "..."}`) | - | +| `modelRoles` | Model assignments by role (e.g., `{"default": "...", "slow": "...", "smol": "...", "plan": "..."}`) | - | | `defaultThinkingLevel` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` | - | | `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - | | `queueMode` | Message queue mode: `all` or `one-at-a-time` | `one-at-a-time` | diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 735a88e48..d21e3db5b 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -15,6 +15,7 @@ export interface Args { model?: string; smol?: string; slow?: string; + plan?: string; apiKey?: string; systemPrompt?: string; appendSystemPrompt?: string; @@ -85,6 +86,8 @@ export function parseArgs(args: string[], extensionFlags?: Map Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2") --smol Smol/fast model for lightweight tasks (or OMP_SMOL_MODEL env) --slow Slow/reasoning model for thorough analysis (or OMP_SLOW_MODEL env) + --plan Plan model for architectural planning (or OMP_PLAN_MODEL env) --api-key API key (defaults to env vars) --system-prompt System prompt (default: coding assistant prompt) --append-system-prompt Append text or file contents to the system prompt diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 61cb4cf47..830cc44d4 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -628,10 +628,12 @@ export async function main(args: string[]) { // Apply model role overrides from CLI args or env vars (ephemeral, not persisted) const smolModel = parsed.smol ?? process.env.OMP_SMOL_MODEL; const slowModel = parsed.slow ?? process.env.OMP_SLOW_MODEL; - if (smolModel || slowModel) { + const planModel = parsed.plan ?? process.env.OMP_PLAN_MODEL; + if (smolModel || slowModel || planModel) { const roleOverrides: Record = {}; if (smolModel) roleOverrides.smol = smolModel; if (slowModel) roleOverrides.slow = slowModel; + if (planModel) roleOverrides.plan = planModel; settingsManager.applyOverrides({ modelRoles: roleOverrides }); } diff --git a/packages/coding-agent/src/modes/components/model-selector.ts b/packages/coding-agent/src/modes/components/model-selector.ts index ad71cbd59..6f545e216 100644 --- a/packages/coding-agent/src/modes/components/model-selector.ts +++ b/packages/coding-agent/src/modes/components/model-selector.ts @@ -35,7 +35,7 @@ interface ScopedModelItem { thinkingLevel: string; } -type ModelRole = "default" | "smol" | "slow" | "temporary"; +type ModelRole = "default" | "smol" | "slow" | "plan" | "temporary"; interface MenuAction { label: string; @@ -46,6 +46,7 @@ const MENU_ACTIONS: MenuAction[] = [ { label: "Set as Default", role: "default" }, { label: "Set as Smol (Fast)", role: "smol" }, { label: "Set as Slow (Thinking)", role: "slow" }, + { label: "Set as Plan (Architect)", role: "plan" }, ]; const ALL_TAB = "ALL"; @@ -78,6 +79,7 @@ export class ModelSelectorComponent extends Container { private defaultModel?: Model; private smolModel?: Model; private slowModel?: Model; + private planModel?: Model; private settingsManager: SettingsManager; private modelRegistry: ModelRegistry; private onSelectCallback: (model: Model, role: string) => void; @@ -209,10 +211,19 @@ export class ModelSelectorComponent extends Container { this.slowModel = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id); } } + + // Load plan model + const planStr = roles.plan; + if (planStr) { + const parsed = parseModelString(planStr); + if (parsed) { + this.planModel = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id); + } + } } private sortModels(models: ModelItem[]): void { - // Sort: tagged models (default/smol/slow) first, then MRU, then alphabetical + // Sort: tagged models (default/smol/slow/plan) first, then MRU, then alphabetical const mruOrder = this.settingsManager.getStorage()?.getModelUsageOrder() ?? []; const mruIndex = new Map(mruOrder.map((key, i) => [key, i])); @@ -220,21 +231,25 @@ export class ModelSelectorComponent extends Container { const aKey = `${a.provider}/${a.id}`; const bKey = `${b.provider}/${b.id}`; - // Tagged models first: default (0), smol (1), slow (2), untagged (3) + // Tagged models first: default (0), smol (1), slow (2), plan (3), untagged (4) const aTag = modelsAreEqual(this.defaultModel, a.model) ? 0 : modelsAreEqual(this.smolModel, a.model) ? 1 : modelsAreEqual(this.slowModel, a.model) ? 2 - : 3; + : modelsAreEqual(this.planModel, a.model) + ? 3 + : 4; const bTag = modelsAreEqual(this.defaultModel, b.model) ? 0 : modelsAreEqual(this.smolModel, b.model) ? 1 : modelsAreEqual(this.slowModel, b.model) ? 2 - : 3; + : modelsAreEqual(this.planModel, b.model) + ? 3 + : 4; if (aTag !== bTag) return aTag - bTag; // Then MRU order (models in mruIndex come before those not in it) @@ -380,12 +395,14 @@ export class ModelSelectorComponent extends Container { const isDefault = modelsAreEqual(this.defaultModel, item.model); const isSmol = modelsAreEqual(this.smolModel, item.model); const isSlow = modelsAreEqual(this.slowModel, item.model); + const isPlan = modelsAreEqual(this.planModel, item.model); // Build role badges (inverted: color as background, black text) const badges: string[] = []; if (isDefault) badges.push(makeInvertedBadge("DEFAULT", "success")); if (isSmol) badges.push(makeInvertedBadge("SMOL", "warning")); if (isSlow) badges.push(makeInvertedBadge("SLOW", "accent")); + if (isPlan) badges.push(makeInvertedBadge("PLAN", "muted")); const badgeText = badges.length > 0 ? ` ${badges.join(" ")}` : ""; let line = ""; @@ -585,6 +602,8 @@ export class ModelSelectorComponent extends Container { this.smolModel = model; } else if (role === "slow") { this.slowModel = model; + } else if (role === "plan") { + this.planModel = model; } // Notify caller (for updating agent state if needed) diff --git a/packages/coding-agent/src/prompts/agents/plan.md b/packages/coding-agent/src/prompts/agents/plan.md index 19e5ff363..98f38f7b7 100644 --- a/packages/coding-agent/src/prompts/agents/plan.md +++ b/packages/coding-agent/src/prompts/agents/plan.md @@ -3,7 +3,7 @@ name: plan description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls. tools: read, grep, find, ls, bash spawns: explore -model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt +model: pi/plan, pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt --- diff --git a/packages/coding-agent/src/prompts/tools/todo-write.md b/packages/coding-agent/src/prompts/tools/todo-write.md index dc3ff0b1b..f3123afb1 100644 --- a/packages/coding-agent/src/prompts/tools/todo-write.md +++ b/packages/coding-agent/src/prompts/tools/todo-write.md @@ -9,20 +9,19 @@ Use this tool proactively in these scenarios: 3. User explicitly requests todo list - When the user directly asks you to use the todo list 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated) 5. After receiving new instructions - Immediately capture user requirements as todos -6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time +6. When you start working on a task - Mark it as in_progress BEFORE beginning work 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation 1. **Task States**: Use these states to track progress: - pending: Task not yet started - - in_progress: Currently working on (limit to ONE task at a time) + - in_progress: Currently working on - completed: Task finished successfully 2. **Task Management**: - Update task status in real-time as you work - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) - - Exactly ONE task must be in_progress at any time (not less, not more) - - Complete current tasks before starting new ones + - Multiple tasks may be in_progress simultaneously when working in parallel - Remove tasks that are no longer relevant from the list entirely 3. **Task Completion Requirements**: - ONLY mark a task as completed when you have FULLY accomplished it diff --git a/packages/coding-agent/src/tools/todo-write.ts b/packages/coding-agent/src/tools/todo-write.ts index e27f9c287..47c4647d1 100644 --- a/packages/coding-agent/src/tools/todo-write.ts +++ b/packages/coding-agent/src/tools/todo-write.ts @@ -99,13 +99,13 @@ function validateSequentialTodos(todos: TodoItem[]): { valid: boolean; error?: s return acc; }, []); - if (inProgressIndices.length > 1) { - return { valid: false, error: "Only one todo can be in progress at a time." }; - } - - if (inProgressIndices.length === 1 && firstIncompleteIndex >= 0) { - if (inProgressIndices[0] !== firstIncompleteIndex) { - return { valid: false, error: "Todo in progress must be the next incomplete item." }; + for (const idx of inProgressIndices) { + const hasPriorIncomplete = todos.slice(0, idx).some(t => t.status === "pending"); + if (hasPriorIncomplete) { + return { + valid: false, + error: `Cannot start "${todos[idx].content}" while earlier tasks are still pending.`, + }; } }