diff --git a/package.json b/package.json index a09f8d6..5d4e650 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mikefreno/omp-pygienium", - "version": "0.1.1", + "version": "0.1.2", "description": "Code hygiene extension for omp (port of the pi extension) — isolated sub-agent checks that scan a target, apply fixes, and emit a findings+changes report.", "keywords": [ "omp", diff --git a/src/agent-runner.ts b/src/agent-runner.ts index 00a4735..af4773a 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -20,6 +20,7 @@ import { dirname, isAbsolute, join } from "node:path"; import type { AgentSession, AgentSessionEvent, + ExtensionCommandContext, } from "@oh-my-pi/pi-coding-agent"; import { loadAgents, extensionRoot, type AgentDef } from "./agents.js"; @@ -52,6 +53,11 @@ export interface AgentTaskOptions { task: string; /** Optional tool allowlist override (else uses the agent's `allowedTools`). */ allowedTools?: string[]; + /** + * The currently selected model from the invoking session. When omitted, + * `createAgentSession` falls back to the settings default model. + */ + model?: ExtensionCommandContext["model"]; /** Optional explicit agent definition (skips `loadAgents`). */ agent?: AgentDef; /** @@ -196,6 +202,9 @@ export async function defaultAgentRunner( const { session } = await createAgentSession({ cwd: opts.cwd, + // Pin the sub-agent to the model the user has selected in the invoking + // session rather than the settings default. + ...(opts.model ? { model: opts.model } : {}), toolNames: tools, // `tools` is an allowlist, not a request list. restrictToolNames: true, diff --git a/src/commands.ts b/src/commands.ts index 43f78fc..1430767 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -48,7 +48,7 @@ import type { SendChatMessage } from "./phases.js"; /** Narrow context slice handlers need (a subset of `ExtensionCommandContext`). */ export type PygieniumCtx = Pick< ExtensionCommandContext, - "cwd" | "hasUI" | "ui" + "cwd" | "hasUI" | "ui" | "model" > & { /** Optional callback to post messages to the chat window. */ sendChatMessage?: SendChatMessage; @@ -176,6 +176,7 @@ export async function handleCheckCommand( existingState: existing, ui: ctx.ui, hasUI: ctx.hasUI, + model: ctx.model, sendChatMessage: ctx.sendChatMessage, onAgentEvent: ctx.onAgentEvent, sendPhaseLine: ctx.sendPhaseLine, @@ -215,6 +216,7 @@ export async function handleAllCommand( only: parsed.only, ui: ctx.ui, hasUI: ctx.hasUI, + model: ctx.model, sendChatMessage: ctx.sendChatMessage, onAgentEvent: ctx.onAgentEvent, sendPhaseLine: ctx.sendPhaseLine, @@ -303,6 +305,7 @@ export async function handleResumeCommand( scope: { cwd, target: cwd, fix: entry.fix, rest: [] }, ui: ctx.ui, hasUI: ctx.hasUI, + model: ctx.model, existingState: state, sendChatMessage: ctx.sendChatMessage, onAgentEvent: ctx.onAgentEvent, diff --git a/src/index.ts b/src/index.ts index 6b56719..fcd6602 100644 --- a/src/index.ts +++ b/src/index.ts @@ -409,6 +409,7 @@ export default async function pygieniumExtension( cwd: ctx.cwd, hasUI: ctx.hasUI, ui: ctx.ui, + model: ctx.model, sendChatMessage, onAgentEvent, sendPhaseLine, diff --git a/src/modes/all.ts b/src/modes/all.ts index c9122b6..9941bb6 100644 --- a/src/modes/all.ts +++ b/src/modes/all.ts @@ -22,10 +22,13 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; -import type { ExtensionUIContext } from "@oh-my-pi/pi-coding-agent"; +import type { + AgentSessionEvent, + ExtensionCommandContext, + ExtensionUIContext, +} from "@oh-my-pi/pi-coding-agent"; import { getAllChecks, type CheckDefinition } from "../checks/registry.js"; import { runCheck } from "./check-runner.js"; -import type { AgentSessionEvent } from "@oh-my-pi/pi-coding-agent"; import { createPhaseStrip, type SendChatMessage } from "../phases.js"; import { createPipelineFooter, type ItemStatus } from "../footer.js"; import { runRecon } from "../recon.js"; @@ -74,6 +77,12 @@ export interface AllRunOptions { ui?: ExtensionUIContext; /** Whether dialog-capable UI is available. */ hasUI?: boolean; + /** + * The currently selected model from the invoking session; forwarded to + * each check's sub-agents so scans run on the model the user picked, not + * the settings default. + */ + model?: ExtensionCommandContext["model"]; /** Optional callback to post completion messages into the chat. */ sendChatMessage?: SendChatMessage; /** Optional callback forwarding raw sub-agent events to the chat stream. */ @@ -369,6 +378,7 @@ export async function runAllChecks( scope: { cwd, target, fix, rest: [] }, ui: opts.ui, hasUI, + model: opts.model, existingState: state, sendChatMessage: opts.sendChatMessage, onAgentEvent: opts.onAgentEvent, diff --git a/src/modes/check-runner.ts b/src/modes/check-runner.ts index ad76d41..c562eb7 100644 --- a/src/modes/check-runner.ts +++ b/src/modes/check-runner.ts @@ -17,7 +17,10 @@ import { rm } from "node:fs/promises"; import { resolve, join } from "node:path"; -import type { ExtensionUIContext } from "@oh-my-pi/pi-coding-agent"; +import type { + ExtensionCommandContext, + ExtensionUIContext, +} from "@oh-my-pi/pi-coding-agent"; import type { CheckDefinition, CheckScope } from "../checks/registry.js"; import { runAgentTask } from "../agent-runner.js"; import { runRecon } from "../recon.js"; @@ -84,6 +87,12 @@ export interface RunCheckOptions { ui?: ExtensionUIContext; /** Whether dialog-capable UI is available. */ hasUI?: boolean; + /** + * The currently selected model from the invoking session; forwarded to + * each sub-agent so scans run on the model the user picked, not the + * settings default. + */ + model?: ExtensionCommandContext["model"]; /** Pre-existing run state to update (for `/pygienium-all` and resume). */ existingState?: RunState; /** Optional callback to post completion messages into the chat. */ @@ -278,6 +287,7 @@ async function runCheckImplInner( cwd: scope.target, agentName: check.agentName, task: scanTask, + model: opts.model, onEvent: forward(PHASE_ANALYSIS), }); findings = scanResult.text; @@ -310,6 +320,7 @@ async function runCheckImplInner( cwd: scope.target, agentName: check.fixAgentName ?? "fixer", task: fixTask, + model: opts.model, onEvent: forward(PHASE_FIX), }); changes = fixResult.text; diff --git a/tests/check-runner.test.ts b/tests/check-runner.test.ts index a38a519..79e1dd4 100644 --- a/tests/check-runner.test.ts +++ b/tests/check-runner.test.ts @@ -110,6 +110,28 @@ describe("check-runner integration", () => { ); }); + it("forwards the selected model to every sub-agent", async () => { + const check = smokeCheck(); + const selectedModel = { + provider: "test-provider", + id: "test-model", + } as unknown as PygieniumCtx["model"]; + const seen: unknown[] = []; + setAgentRunner(async (opts) => { + seen.push(opts.model); + return fakeAgentRunner(opts); + }); + await handleCheckCommand( + check, + "--fix", + { ...stubCtx(cwd), model: selectedModel } as PygieniumCtx, + ); + + // Analysis + fix phases each dispatch one sub-agent. + expect(seen.length).toBe(2); + for (const m of seen) expect(m).toBe(selectedModel); + }); + it("persists run-state.json at the expected path", async () => { const check = smokeCheck(); await handleCheckCommand(check, "", stubCtx(cwd));