Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/plugin-handlers/agent-config-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import type { OhMyOpenCodeConfig } from "../config"
import * as agentLoader from "../features/claude-code-agent-loader"
import * as skillLoader from "../features/opencode-skill-loader"
import type { LoadedSkill } from "../features/opencode-skill-loader"
import { getAgentListDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"
import { getAgentDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"
import { applyAgentConfig } from "./agent-config-handler"
import type { PluginComponents } from "./plugin-components-loader"

const BUILTIN_SISYPHUS_DISPLAY_NAME = getAgentListDisplayName("sisyphus")
const BUILTIN_SISYPHUS_JUNIOR_DISPLAY_NAME = getAgentListDisplayName("sisyphus-junior")
const BUILTIN_MULTIMODAL_LOOKER_DISPLAY_NAME = getAgentListDisplayName("multimodal-looker")
const BUILTIN_SISYPHUS_DISPLAY_NAME = getAgentDisplayName("sisyphus")
const BUILTIN_SISYPHUS_JUNIOR_DISPLAY_NAME = getAgentDisplayName("sisyphus-junior")
const BUILTIN_MULTIMODAL_LOOKER_DISPLAY_NAME = getAgentDisplayName("multimodal-looker")

function createPluginComponents(): PluginComponents {
return {
Expand Down
6 changes: 3 additions & 3 deletions src/plugin-handlers/agent-config-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createBuiltinAgents } from "../agents";
import { createSisyphusJuniorAgentWithOverrides } from "../agents/sisyphus-junior";
import type { OhMyOpenCodeConfig } from "../config";
import { isTaskSystemEnabled, log, migrateAgentConfig } from "../shared";
import { getAgentRuntimeName } from "../shared/agent-display-names";
import { getAgentDisplayName, getAgentRuntimeName } from "../shared/agent-display-names";
import { AGENT_NAME_MAP } from "../shared/migration";
import { registerAgentName } from "../features/claude-code-session-state";
import {
Expand Down Expand Up @@ -159,10 +159,10 @@ export async function applyAgentConfig(params: {
if (isSisyphusEnabled && builtinAgents.sisyphus) {
if (configuredDefaultAgent) {
(params.config as { default_agent?: string }).default_agent =
getAgentRuntimeName(configuredDefaultAgent);
getAgentDisplayName(configuredDefaultAgent);
} else {
(params.config as { default_agent?: string }).default_agent =
getAgentRuntimeName("sisyphus");
getAgentDisplayName("sisyphus");
}

// Assembly order: Sisyphus -> Hephaestus -> Prometheus -> Atlas
Expand Down
99 changes: 75 additions & 24 deletions src/plugin-handlers/agent-key-remapper.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
import { describe, it, expect } from "bun:test"
import { remapAgentKeysToDisplayNames } from "./agent-key-remapper"
import { getAgentDisplayName, getAgentListDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"
import { getAgentDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"

const ZWSP_REGEX = /[\u200B\u200C\u200D\uFEFF]/

describe("remapAgentKeysToDisplayNames", () => {
it("object keys must not contain ZWSP characters (RFC 7230)", () => {
// given all core agents with ZWSP-based ordering
const agents = {
sisyphus: { prompt: "test" },
hephaestus: { prompt: "test" },
prometheus: { prompt: "test" },
atlas: { prompt: "test" },
}

// when remapping
const result = remapAgentKeysToDisplayNames(agents)

// then NO object key should contain ZWSP (RFC 7230 compliance)
for (const key of Object.keys(result)) {
expect(key).not.toMatch(ZWSP_REGEX)
}
})

it("name field MUST contain ZWSP for core agents (OpenCode sort ordering)", () => {
// given core agents
const agents = {
sisyphus: { prompt: "test" },
hephaestus: { prompt: "test" },
prometheus: { prompt: "test" },
atlas: { prompt: "test" },
}

// when remapping
const result = remapAgentKeysToDisplayNames(agents)

// then name fields MUST have ZWSP prefixes for sort ordering
const sisyphusConfig = result[getAgentDisplayName("sisyphus")] as Record<string, unknown>
const hephaestusConfig = result[getAgentDisplayName("hephaestus")] as Record<string, unknown>
const prometheusConfig = result[getAgentDisplayName("prometheus")] as Record<string, unknown>
const atlasConfig = result[getAgentDisplayName("atlas")] as Record<string, unknown>

expect(sisyphusConfig.name).toMatch(ZWSP_REGEX)
expect(hephaestusConfig.name).toMatch(ZWSP_REGEX)
expect(prometheusConfig.name).toMatch(ZWSP_REGEX)
expect(atlasConfig.name).toMatch(ZWSP_REGEX)

// And they should be the runtime names (with ZWSP)
expect(sisyphusConfig.name).toBe(getAgentRuntimeName("sisyphus"))
expect(hephaestusConfig.name).toBe(getAgentRuntimeName("hephaestus"))
expect(prometheusConfig.name).toBe(getAgentRuntimeName("prometheus"))
expect(atlasConfig.name).toBe(getAgentRuntimeName("atlas"))
})


it("remaps known agent keys to display names", () => {
// given agents with lowercase keys
const agents = {
Expand All @@ -14,7 +65,7 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)

// then known agents get display name keys only
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["oracle"]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
})
Expand Down Expand Up @@ -49,13 +100,13 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)

// then all get display name keys
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
expect(result[getAgentListDisplayName("hephaestus")]).toBeDefined()
expect(result[getAgentDisplayName("hephaestus")]).toBeDefined()
expect(result["hephaestus"]).toBeUndefined()
expect(result[getAgentListDisplayName("prometheus")]).toBeDefined()
expect(result[getAgentDisplayName("prometheus")]).toBeDefined()
expect(result["prometheus"]).toBeUndefined()
expect(result[getAgentListDisplayName("atlas")]).toBeDefined()
expect(result[getAgentDisplayName("atlas")]).toBeDefined()
expect(result["atlas"]).toBeUndefined()
expect(result[getAgentDisplayName("athena")]).toBeDefined()
expect(result["athena"]).toBeUndefined()
Expand All @@ -77,8 +128,8 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)

// then only display key is emitted
expect(Object.keys(result)).toEqual([getAgentListDisplayName("sisyphus")])
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(Object.keys(result)).toEqual([getAgentDisplayName("sisyphus")])
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
})

Expand All @@ -96,10 +147,10 @@ describe("remapAgentKeysToDisplayNames", () => {

// then
expect(remappedNames).toEqual([
getAgentListDisplayName("atlas"),
getAgentListDisplayName("prometheus"),
getAgentListDisplayName("hephaestus"),
getAgentListDisplayName("sisyphus"),
getAgentDisplayName("atlas"),
getAgentDisplayName("prometheus"),
getAgentDisplayName("hephaestus"),
getAgentDisplayName("sisyphus"),
])
})

Expand All @@ -118,27 +169,27 @@ describe("remapAgentKeysToDisplayNames", () => {

// then keys and names both use the same runtime-facing list names
expect(Object.keys(result).slice(0, 4)).toEqual([
getAgentListDisplayName("sisyphus"),
getAgentListDisplayName("hephaestus"),
getAgentListDisplayName("prometheus"),
getAgentListDisplayName("atlas"),
getAgentDisplayName("sisyphus"),
getAgentDisplayName("hephaestus"),
getAgentDisplayName("prometheus"),
getAgentDisplayName("atlas"),
])
expect(result[getAgentListDisplayName("sisyphus")]).toEqual({
expect(result[getAgentDisplayName("sisyphus")]).toEqual({
name: getAgentRuntimeName("sisyphus"),
prompt: "test",
mode: "primary",
})
expect(result[getAgentListDisplayName("hephaestus")]).toEqual({
expect(result[getAgentDisplayName("hephaestus")]).toEqual({
name: getAgentRuntimeName("hephaestus"),
prompt: "test",
mode: "primary",
})
expect(result[getAgentListDisplayName("prometheus")]).toEqual({
expect(result[getAgentDisplayName("prometheus")]).toEqual({
name: getAgentRuntimeName("prometheus"),
prompt: "test",
mode: "all",
})
expect(result[getAgentListDisplayName("atlas")]).toEqual({
expect(result[getAgentDisplayName("atlas")]).toEqual({
name: getAgentRuntimeName("atlas"),
prompt: "test",
mode: "primary",
Expand All @@ -159,22 +210,22 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)

// then runtime-facing names stay aligned even when builtin configs omit name
expect(result[getAgentListDisplayName("sisyphus")]).toEqual({
expect(result[getAgentDisplayName("sisyphus")]).toEqual({
name: getAgentRuntimeName("sisyphus"),
prompt: "test",
mode: "primary",
})
expect(result[getAgentListDisplayName("hephaestus")]).toEqual({
expect(result[getAgentDisplayName("hephaestus")]).toEqual({
name: getAgentRuntimeName("hephaestus"),
prompt: "test",
mode: "primary",
})
expect(result[getAgentListDisplayName("prometheus")]).toEqual({
expect(result[getAgentDisplayName("prometheus")]).toEqual({
name: getAgentRuntimeName("prometheus"),
prompt: "test",
mode: "all",
})
expect(result[getAgentListDisplayName("atlas")]).toEqual({
expect(result[getAgentDisplayName("atlas")]).toEqual({
name: getAgentRuntimeName("atlas"),
prompt: "test",
mode: "primary",
Expand Down
4 changes: 2 additions & 2 deletions src/plugin-handlers/agent-key-remapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAgentListDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"
import { getAgentDisplayName, getAgentRuntimeName } from "../shared/agent-display-names"

function rewriteAgentNameForListDisplay(
key: string,
Expand All @@ -21,7 +21,7 @@ export function remapAgentKeysToDisplayNames(
const result: Record<string, unknown> = {}

for (const [key, value] of Object.entries(agents)) {
const displayName = getAgentListDisplayName(key)
const displayName = getAgentDisplayName(key)
if (displayName && displayName !== key) {
result[displayName] = rewriteAgentNameForListDisplay(key, value)
// Regression guard: do not also assign result[key].
Expand Down
12 changes: 6 additions & 6 deletions src/plugin-handlers/agent-priority-order.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
reorderAgentsByPriority,
CANONICAL_CORE_AGENT_ORDER,
} from "./agent-priority-order"
import { getAgentDisplayName, getAgentListDisplayName } from "../shared/agent-display-names"
import { getAgentDisplayName } from "../shared/agent-display-names"

describe("agent-priority-order", () => {
describe("CANONICAL_CORE_AGENT_ORDER", () => {
Expand Down Expand Up @@ -35,11 +35,11 @@ describe("agent-priority-order", () => {
})

describe("reorderAgentsByPriority", () => {
// given: display names for all core agents
const sisyphus = getAgentListDisplayName("sisyphus")
const hephaestus = getAgentListDisplayName("hephaestus")
const prometheus = getAgentListDisplayName("prometheus")
const atlas = getAgentListDisplayName("atlas")
// given: display names for all core agents (no ZWSP in keys)
const sisyphus = getAgentDisplayName("sisyphus")
const hephaestus = getAgentDisplayName("hephaestus")
const prometheus = getAgentDisplayName("prometheus")
const atlas = getAgentDisplayName("atlas")
const oracle = getAgentDisplayName("oracle")
const librarian = getAgentDisplayName("librarian")
const explore = getAgentDisplayName("explore")
Expand Down
4 changes: 2 additions & 2 deletions src/plugin-handlers/agent-priority-order.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAgentListDisplayName } from "../shared/agent-display-names"
import { getAgentDisplayName } from "../shared/agent-display-names"

/**
* CRITICAL: This is the ONLY source of truth for core agent ordering.
Expand All @@ -25,7 +25,7 @@ const CORE_AGENT_ORDER: ReadonlyArray<{
order: number
}> = CANONICAL_CORE_AGENT_ORDER.map((configKey, index) => ({
configKey,
displayName: getAgentListDisplayName(configKey),
displayName: getAgentDisplayName(configKey),
order: index + 1,
}))

Expand Down
13 changes: 5 additions & 8 deletions src/plugin-handlers/command-config-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import * as skillLoader from "../features/opencode-skill-loader";
import type { OhMyOpenCodeConfig } from "../config";
import type { PluginComponents } from "./plugin-components-loader";
import { applyCommandConfig } from "./command-config-handler";
import {
getAgentDisplayName,
getAgentListDisplayName,
} from "../shared/agent-display-names";
import { getAgentDisplayName } from "../shared/agent-display-names";

function createPluginComponents(): PluginComponents {
return {
Expand Down Expand Up @@ -108,7 +105,7 @@ describe("applyCommandConfig", () => {
expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill");
});

test("normalizes Atlas command agents to the runtime list name used by opencode command routing", async () => {
test("normalizes Atlas command agents to the display name for HTTP-safe routing", async () => {
// given
loadBuiltinCommandsSpy.mockReturnValue({
"start-work": {
Expand All @@ -130,10 +127,10 @@ describe("applyCommandConfig", () => {

// then
const commandConfig = config.command as Record<string, { agent?: string }>;
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"));
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"));
});

test("normalizes legacy display-name command agents to the runtime list name", async () => {
test("normalizes legacy display-name command agents to the display name", async () => {
// given
loadBuiltinCommandsSpy.mockReturnValue({
"start-work": {
Expand All @@ -155,6 +152,6 @@ describe("applyCommandConfig", () => {

// then
const commandConfig = config.command as Record<string, { agent?: string }>;
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"));
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"));
});
});
4 changes: 2 additions & 2 deletions src/plugin-handlers/command-config-handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { OhMyOpenCodeConfig } from "../config";
import {
getAgentConfigKey,
getAgentListDisplayName,
getAgentDisplayName,
} from "../shared/agent-display-names";
import {
loadUserCommands,
Expand Down Expand Up @@ -99,7 +99,7 @@ export async function applyCommandConfig(params: {
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
for (const cmd of Object.values(commands)) {
if (cmd?.agent && typeof cmd.agent === "string") {
cmd.agent = getAgentListDisplayName(getAgentConfigKey(cmd.agent));
cmd.agent = getAgentDisplayName(getAgentConfigKey(cmd.agent));
}
}
}
Loading
Loading