This commit is contained in:
omp-port
2026-08-21 21:19:45 -04:00
parent f45e7ffb0e
commit c080ebcd1e
7 changed files with 61 additions and 5 deletions

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,

View File

@@ -409,6 +409,7 @@ export default async function pygieniumExtension(
cwd: ctx.cwd,
hasUI: ctx.hasUI,
ui: ctx.ui,
model: ctx.model,
sendChatMessage,
onAgentEvent,
sendPhaseLine,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));