From 9ba676330a0a45a9ee346bbfe6026e9f90f71956 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 18:55:47 +0900 Subject: [PATCH 1/3] fix(call-omo-agent): validate missing subagent type Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/tools/call-omo-agent/tools.test.ts | 13 +++++++++++++ src/tools/call-omo-agent/tools.ts | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/tools/call-omo-agent/tools.test.ts b/src/tools/call-omo-agent/tools.test.ts index 17491cb82..eca670399 100644 --- a/src/tools/call-omo-agent/tools.test.ts +++ b/src/tools/call-omo-agent/tools.test.ts @@ -136,6 +136,19 @@ describe("createCallOmoAgent", () => { }) describe("dynamic custom agent resolution", () => { + test("should reject missing subagent_type without throwing", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", run_in_background: true }, + toolCtx + ) + + expect(result).toContain("subagent_type is required") + }) + test("should accept a custom agent returned by client.app.agents()", async () => { const agents = [...DEFAULT_AGENTS, { name: "bug-fixer", mode: "subagent" }] const mockCtx = createMockCtx(agents) diff --git a/src/tools/call-omo-agent/tools.ts b/src/tools/call-omo-agent/tools.ts index 51ea8730c..6e315cd86 100644 --- a/src/tools/call-omo-agent/tools.ts +++ b/src/tools/call-omo-agent/tools.ts @@ -140,6 +140,10 @@ export function createCallOmoAgent( `[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`, ); + if (typeof args.subagent_type !== "string" || args.subagent_type.trim() === "") { + return "Error: subagent_type is required." + } + const callableAgents = await resolveCallableAgents(ctx.client); // Strip ZWSP and case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc. From 9c4bcebb2240ce87303cc609bfed0fc285454d19 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 18:55:57 +0900 Subject: [PATCH 2/3] fix(plugin): normalize bare schema refs Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/plugin/normalize-tool-arg-schemas.test.ts | 35 ++++++++++++++++++- src/plugin/normalize-tool-arg-schemas.ts | 13 +++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/plugin/normalize-tool-arg-schemas.test.ts b/src/plugin/normalize-tool-arg-schemas.test.ts index 27f148995..8a9247dda 100644 --- a/src/plugin/normalize-tool-arg-schemas.test.ts +++ b/src/plugin/normalize-tool-arg-schemas.test.ts @@ -6,7 +6,7 @@ import { tmpdir } from "node:os" import { dirname, join } from "node:path" import { pathToFileURL } from "node:url" import { tool } from "@opencode-ai/plugin" -import { normalizeToolArgSchemas } from "./normalize-tool-arg-schemas" +import { normalizeToolArgSchemas, sanitizeJsonSchema } from "./normalize-tool-arg-schemas" const tempDirectories: string[] = [] @@ -95,3 +95,36 @@ describe("normalizeToolArgSchemas", () => { expect(afterQuery?.examples).toEqual(["issue 2314"]) }) }) + +describe("sanitizeJsonSchema", () => { + it("rewrites bare $ref values to $defs JSON pointers", () => { + // given + const schema = { + type: "object", + properties: { + new_encoding: { $ref: "Encoding" }, + existing_pointer: { $ref: "#/$defs/AlreadyValid" }, + }, + $defs: { + Encoding: { type: "string" }, + AlreadyValid: { type: "string" }, + }, + } + + // when + const sanitized = sanitizeJsonSchema(schema) + + // then + expect(sanitized).toEqual({ + type: "object", + properties: { + new_encoding: { $ref: "#/$defs/Encoding" }, + existing_pointer: { $ref: "#/$defs/AlreadyValid" }, + }, + $defs: { + Encoding: { type: "string" }, + AlreadyValid: { type: "string" }, + }, + }) + }) +}) diff --git a/src/plugin/normalize-tool-arg-schemas.ts b/src/plugin/normalize-tool-arg-schemas.ts index 0f626b546..52813bc64 100644 --- a/src/plugin/normalize-tool-arg-schemas.ts +++ b/src/plugin/normalize-tool-arg-schemas.ts @@ -47,6 +47,14 @@ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value) } +function normalizeJsonSchemaRef(value: string): string { + if (value.startsWith("#") || value.includes(":") || value.startsWith("/")) { + return value + } + + return `#/$defs/${value}` +} + export function sanitizeJsonSchema(value: unknown, depth = 0, isPropertyName = false): unknown { if (Array.isArray(value)) { return value.map((item) => sanitizeJsonSchema(item, depth + 1, false)) @@ -67,6 +75,11 @@ export function sanitizeJsonSchema(value: unknown, depth = 0, isPropertyName = f continue } + if (!isPropertyName && key === "$ref" && typeof nestedValue === "string") { + sanitized[key] = normalizeJsonSchemaRef(nestedValue) + continue + } + const childIsPropertyName = key === "properties" && !isPropertyName sanitized[key] = sanitizeJsonSchema(nestedValue, depth + 1, childIsPropertyName) } From c3beb370a5e83f68821ef3b7da3664ecc26401c8 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 18:56:09 +0900 Subject: [PATCH 3/3] fix(doctor): detect gh when Bun.which misses it Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/cli/doctor/checks/tools-gh.test.ts | 35 ++++++++++++++++++++++++++ src/cli/doctor/checks/tools-gh.ts | 14 +++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/cli/doctor/checks/tools-gh.test.ts diff --git a/src/cli/doctor/checks/tools-gh.test.ts b/src/cli/doctor/checks/tools-gh.test.ts new file mode 100644 index 000000000..46eec87e5 --- /dev/null +++ b/src/cli/doctor/checks/tools-gh.test.ts @@ -0,0 +1,35 @@ +/// + +import { afterEach, describe, expect, it, mock } from "bun:test" + +const originalWhich = Bun.which + +afterEach(() => { + Bun.which = originalWhich + mock.restore() +}) + +describe("getGhCliInfo", () => { + it("falls back to gh --version when Bun.which cannot find gh", async () => { + // given + Bun.which = mock(() => null) + mock.module("../spawn-with-timeout", () => ({ + spawnWithTimeout: mock((command: string[]) => { + if (command.join(" ") === "gh --version") { + return Promise.resolve({ stdout: "gh version 2.82.1\n", stderr: "", exitCode: 0, timedOut: false }) + } + + return Promise.resolve({ stdout: "", stderr: "not logged in", exitCode: 1, timedOut: false }) + }), + })) + const { getGhCliInfo } = await import("./tools-gh") + + // when + const info = await getGhCliInfo() + + // then + expect(info.installed).toBe(true) + expect(info.version).toBe("2.82.1") + expect(info.path).toBe(null) + }) +}) diff --git a/src/cli/doctor/checks/tools-gh.ts b/src/cli/doctor/checks/tools-gh.ts index 71a539d1e..6839a71fc 100644 --- a/src/cli/doctor/checks/tools-gh.ts +++ b/src/cli/doctor/checks/tools-gh.ts @@ -80,6 +80,20 @@ async function getGhAuthStatus(): Promise<{ export async function getGhCliInfo(): Promise { const binaryStatus = await checkBinaryExists("gh") if (!binaryStatus.exists) { + const version = await getGhVersion() + if (version) { + const authStatus = await getGhAuthStatus() + return { + installed: true, + version, + path: null, + authenticated: authStatus.authenticated, + username: authStatus.username, + scopes: authStatus.scopes, + error: authStatus.error, + } + } + return { installed: false, version: null,