diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index c443ff7e2..327536459 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -162,6 +162,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -207,6 +210,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -294,6 +300,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -335,6 +344,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -380,6 +392,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -467,6 +482,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -508,6 +526,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -553,6 +574,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -640,6 +664,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -681,6 +708,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -726,6 +756,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -813,6 +846,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -854,6 +890,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -899,6 +938,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -986,6 +1028,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1027,6 +1072,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1072,6 +1120,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -1159,6 +1210,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1200,6 +1254,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1245,6 +1302,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -1332,6 +1392,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1373,6 +1436,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1418,6 +1484,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -1505,6 +1574,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1546,6 +1618,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1591,6 +1666,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -1678,6 +1756,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1719,6 +1800,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1764,6 +1848,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -1851,6 +1938,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -1892,6 +1982,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -1937,6 +2030,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -2024,6 +2120,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -2065,6 +2164,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -2110,6 +2212,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -2197,6 +2302,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -2238,6 +2346,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -2283,6 +2394,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -2370,6 +2484,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -2411,6 +2528,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -2456,6 +2576,9 @@ }, { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string", "enum": [ @@ -2543,6 +2666,9 @@ }, "providerOptions": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} } }, @@ -2553,6 +2679,9 @@ }, "categories": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { @@ -2616,6 +2745,9 @@ }, "tools": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -2656,6 +2788,9 @@ }, "plugins_override": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "boolean" } @@ -2926,6 +3061,9 @@ }, "metadata": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} }, "allowed-tools": { @@ -2977,6 +3115,9 @@ }, "providerConcurrency": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "number", "minimum": 0 @@ -2984,6 +3125,9 @@ }, "modelConcurrency": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "number", "minimum": 0 diff --git a/src/plugin-handlers/agent-key-remapper.test.ts b/src/plugin-handlers/agent-key-remapper.test.ts new file mode 100644 index 000000000..fe78ea739 --- /dev/null +++ b/src/plugin-handlers/agent-key-remapper.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect } from "bun:test" +import { remapAgentKeysToDisplayNames } from "./agent-key-remapper" + +describe("remapAgentKeysToDisplayNames", () => { + it("remaps known agent keys to display names", () => { + // given agents with lowercase keys + const agents = { + sisyphus: { prompt: "test", mode: "primary" }, + oracle: { prompt: "test", mode: "subagent" }, + } + + // when remapping + const result = remapAgentKeysToDisplayNames(agents) + + // then known agents get display name keys + expect(result["Sisyphus (Ultraworker)"]).toBeDefined() + expect(result["oracle"]).toBeDefined() + expect(result["sisyphus"]).toBeUndefined() + }) + + it("preserves unknown agent keys unchanged", () => { + // given agents with a custom key + const agents = { + "custom-agent": { prompt: "custom" }, + } + + // when remapping + const result = remapAgentKeysToDisplayNames(agents) + + // then custom key is unchanged + expect(result["custom-agent"]).toBeDefined() + }) + + it("remaps all core agents", () => { + // given all core agents + const agents = { + sisyphus: {}, + hephaestus: {}, + prometheus: {}, + atlas: {}, + metis: {}, + momus: {}, + "sisyphus-junior": {}, + } + + // when remapping + const result = remapAgentKeysToDisplayNames(agents) + + // then all get display name keys + expect(Object.keys(result)).toEqual([ + "Sisyphus (Ultraworker)", + "Hephaestus (Deep Agent)", + "Prometheus (Plan Builder)", + "Atlas (Plan Executor)", + "Metis (Plan Consultant)", + "Momus (Plan Critic)", + "Sisyphus-Junior", + ]) + }) +}) diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index bca4ce4d1..cf6e2461b 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -4,6 +4,7 @@ import { describe, test, expect, spyOn, beforeEach, afterEach } from "bun:test" import { resolveCategoryConfig, createConfigHandler } from "./config-handler" import type { CategoryConfig } from "../config/schema" import type { OhMyOpenCodeConfig } from "../config" +import { getAgentDisplayName } from "../shared/agent-display-names" import * as agents from "../agents" import * as sisyphusJunior from "../agents/sisyphus-junior" @@ -123,7 +124,7 @@ describe("Sisyphus-Junior model inheritance", () => { // #then const agentConfig = config.agent as Record - expect(agentConfig["sisyphus-junior"]?.model).toBe( + expect(agentConfig[getAgentDisplayName("sisyphus-junior")]?.model).toBe( sisyphusJunior.SISYPHUS_JUNIOR_DEFAULTS.model ) }) @@ -155,7 +156,7 @@ describe("Sisyphus-Junior model inheritance", () => { // #then const agentConfig = config.agent as Record - expect(agentConfig["sisyphus-junior"]?.model).toBe( + expect(agentConfig[getAgentDisplayName("sisyphus-junior")]?.model).toBe( "openai/gpt-5.3-codex" ) }) @@ -196,7 +197,12 @@ describe("Plan agent demote behavior", () => { // #then const keys = Object.keys(config.agent as Record) - const coreAgents = ["sisyphus", "hephaestus", "prometheus", "atlas"] + const coreAgents = [ + getAgentDisplayName("sisyphus"), + getAgentDisplayName("hephaestus"), + getAgentDisplayName("prometheus"), + getAgentDisplayName("atlas"), + ] const ordered = keys.filter((key) => coreAgents.includes(key)) expect(ordered).toEqual(coreAgents) }) @@ -236,7 +242,7 @@ describe("Plan agent demote behavior", () => { expect(agents.plan).toBeDefined() expect(agents.plan.mode).toBe("subagent") expect(agents.plan.prompt).toBeUndefined() - expect(agents.prometheus?.prompt).toBeDefined() + expect(agents[getAgentDisplayName("prometheus")]?.prompt).toBeDefined() }) test("plan agent remains unchanged when planner is disabled", async () => { @@ -270,7 +276,7 @@ describe("Plan agent demote behavior", () => { // #then - plan is not touched, prometheus is not created const agents = config.agent as Record - expect(agents.prometheus).toBeUndefined() + expect(agents[getAgentDisplayName("prometheus")]).toBeUndefined() expect(agents.plan).toBeDefined() expect(agents.plan.mode).toBe("primary") expect(agents.plan.prompt).toBe("original plan prompt") @@ -301,8 +307,9 @@ describe("Plan agent demote behavior", () => { // then const agents = config.agent as Record - expect(agents.prometheus).toBeDefined() - expect(agents.prometheus.mode).toBe("all") + const prometheusKey = getAgentDisplayName("prometheus") + expect(agents[prometheusKey]).toBeDefined() + expect(agents[prometheusKey].mode).toBe("all") }) }) @@ -336,8 +343,9 @@ describe("Agent permission defaults", () => { // #then const agentConfig = config.agent as Record }> - expect(agentConfig.hephaestus).toBeDefined() - expect(agentConfig.hephaestus.permission?.task).toBe("allow") + const hephaestusKey = getAgentDisplayName("hephaestus") + expect(agentConfig[hephaestusKey]).toBeDefined() + expect(agentConfig[hephaestusKey].permission?.task).toBe("allow") }) }) @@ -479,8 +487,9 @@ describe("Prometheus direct override priority over category", () => { // then - direct override's reasoningEffort wins const agents = config.agent as Record - expect(agents.prometheus).toBeDefined() - expect(agents.prometheus.reasoningEffort).toBe("low") + const pKey = getAgentDisplayName("prometheus") + expect(agents[pKey]).toBeDefined() + expect(agents[pKey].reasoningEffort).toBe("low") }) test("category reasoningEffort applied when no direct override", async () => { @@ -519,8 +528,9 @@ describe("Prometheus direct override priority over category", () => { // then - category's reasoningEffort is applied const agents = config.agent as Record - expect(agents.prometheus).toBeDefined() - expect(agents.prometheus.reasoningEffort).toBe("high") + const pKey = getAgentDisplayName("prometheus") + expect(agents[pKey]).toBeDefined() + expect(agents[pKey].reasoningEffort).toBe("high") }) test("direct temperature takes priority over category temperature", async () => { @@ -560,8 +570,9 @@ describe("Prometheus direct override priority over category", () => { // then - direct temperature wins over category const agents = config.agent as Record - expect(agents.prometheus).toBeDefined() - expect(agents.prometheus.temperature).toBe(0.1) + const pKey = getAgentDisplayName("prometheus") + expect(agents[pKey]).toBeDefined() + expect(agents[pKey].temperature).toBe(0.1) }) test("prometheus prompt_append is appended to base prompt", async () => { @@ -595,10 +606,11 @@ describe("Prometheus direct override priority over category", () => { // #then - prompt_append is appended to base prompt, not overwriting it const agents = config.agent as Record - expect(agents.prometheus).toBeDefined() - expect(agents.prometheus.prompt).toContain("Prometheus") - expect(agents.prometheus.prompt).toContain(customInstructions) - expect(agents.prometheus.prompt!.endsWith(customInstructions)).toBe(true) + const pKey = getAgentDisplayName("prometheus") + expect(agents[pKey]).toBeDefined() + expect(agents[pKey].prompt).toContain("Prometheus") + expect(agents[pKey].prompt).toContain(customInstructions) + expect(agents[pKey].prompt!.endsWith(customInstructions)).toBe(true) }) }) @@ -947,7 +959,13 @@ describe("config-handler plugin loading error boundary (#1559)", () => { }) describe("per-agent todowrite/todoread deny when task_system enabled", () => { - const PRIMARY_AGENTS = ["sisyphus", "hephaestus", "atlas", "prometheus", "sisyphus-junior"] + const PRIMARY_AGENTS = [ + getAgentDisplayName("sisyphus"), + getAgentDisplayName("hephaestus"), + getAgentDisplayName("atlas"), + getAgentDisplayName("prometheus"), + getAgentDisplayName("sisyphus-junior"), + ] test("denies todowrite and todoread for primary agents when task_system is enabled", async () => { //#given @@ -1021,10 +1039,10 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => { //#then const agentResult = config.agent as Record }> - expect(agentResult.sisyphus?.permission?.todowrite).toBeUndefined() - expect(agentResult.sisyphus?.permission?.todoread).toBeUndefined() - expect(agentResult.hephaestus?.permission?.todowrite).toBeUndefined() - expect(agentResult.hephaestus?.permission?.todoread).toBeUndefined() + expect(agentResult[getAgentDisplayName("sisyphus")]?.permission?.todowrite).toBeUndefined() + expect(agentResult[getAgentDisplayName("sisyphus")]?.permission?.todoread).toBeUndefined() + expect(agentResult[getAgentDisplayName("hephaestus")]?.permission?.todowrite).toBeUndefined() + expect(agentResult[getAgentDisplayName("hephaestus")]?.permission?.todoread).toBeUndefined() }) test("does not deny todowrite/todoread when task_system is undefined", async () => { @@ -1055,7 +1073,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => { //#then const agentResult = config.agent as Record }> - expect(agentResult.sisyphus?.permission?.todowrite).toBeUndefined() - expect(agentResult.sisyphus?.permission?.todoread).toBeUndefined() + expect(agentResult[getAgentDisplayName("sisyphus")]?.permission?.todowrite).toBeUndefined() + expect(agentResult[getAgentDisplayName("sisyphus")]?.permission?.todoread).toBeUndefined() }) })