fix(coding-agent): address second codex pass on vision mode

- read now derives its image behavior from actual tool availability
  (session.isToolActive) with the mode computation as fallback, so
  restricted sessions whose explicit slate omits inspect_image (e.g.
  subagents) never get metadata-only reads pointing at an absent tool
- reconcile passes the post-change availability into the read
  description sync, keeping the advertised prompt correct across flips
  in both directions and when tool construction fails
- flat quoted-dotted inspect_image.mode is normalized into the nested
  target during migration instead of being silently dropped when a
  legacy flat enabled key is present
- regression tests for all three: availability-driven read behavior,
  flat+flat migration, description advertising
This commit is contained in:
alexis@epsilver.xyz
2026-07-27 16:21:48 -04:00
parent 3854c1c3b1
commit b58a943a8e
4 changed files with 77 additions and 11 deletions
@@ -9,6 +9,8 @@ import * as os from "node:os";
import * as path from "node:path";
import type { Model } from "@oh-my-pi/pi-ai";
import { Settings } from "../src/config/settings";
import type { ToolSession } from "../src/tools/index";
import { ReadTool } from "../src/tools/read";
import { isInspectImageToolActive } from "../src/utils/inspect-image-mode";
const visionModel = { provider: "kimi-code", id: "k3", input: ["text", "image"] } as unknown as Model;
@@ -89,5 +91,42 @@ describe("inspect_image.enabled migration", () => {
const settings = await Settings.loadReadOnly({ agentDir, cwd: agentDir });
expect(settings.get("inspect_image.mode")).toBe("off");
});
test("flat explicit mode survives alongside a flat legacy key", async () => {
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-vision-migration-"));
fs.writeFileSync(
path.join(agentDir, "config.yml"),
'"inspect_image.enabled": true\n"inspect_image.mode": "off"\n',
);
const settings = await Settings.loadReadOnly({ agentDir, cwd: agentDir });
expect(settings.get("inspect_image.mode")).toBe("off");
});
});
});
describe("read tool follows actual tool availability", () => {
function readSession(inspectImageActive: boolean): ToolSession {
return {
cwd: os.tmpdir(),
hasUI: false,
settings: Settings.isolated(),
getSessionFile: () => null,
getSessionSpawns: () => null,
getActiveModel: () => textModel,
isToolActive: (name: string) => name === "inspect_image" && inspectImageActive,
} as unknown as ToolSession;
}
test("restricted session (tool absent) never advertises inspect_image", () => {
// auto mode + text-only model would compute active=true, but the tool is
// not in this session's slate, so read must serve inline image blocks.
const tool = new ReadTool(readSession(false));
expect(tool.description).not.toContain("call `inspect_image`");
expect(tool.syncInspectImageState()).toBe(false);
});
test("session with the tool registered advertises it", () => {
const tool = new ReadTool(readSession(true));
expect(tool.syncInspectImageState()).toBe(true);
});
});