fix(start-work): reuse registered opencode agent names
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
getMainSessionID,
|
getMainSessionID,
|
||||||
registerAgentName,
|
registerAgentName,
|
||||||
isAgentRegistered,
|
isAgentRegistered,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
_resetForTesting,
|
_resetForTesting,
|
||||||
} from "./state"
|
} from "./state"
|
||||||
|
|
||||||
@@ -140,6 +141,15 @@ describe("claude-code-session-state", () => {
|
|||||||
expect(isAgentRegistered("Atlas - Plan Executor")).toBe(true)
|
expect(isAgentRegistered("Atlas - Plan Executor")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should resolve config keys back to the registered raw agent name", () => {
|
||||||
|
// given
|
||||||
|
registerAgentName("\u200B\u200B\u200B\u200BAtlas - Plan Executor")
|
||||||
|
|
||||||
|
// when / then
|
||||||
|
expect(resolveRegisteredAgentName("atlas")).toBe("\u200B\u200B\u200B\u200BAtlas - Plan Executor")
|
||||||
|
expect(resolveRegisteredAgentName("Atlas - Plan Executor")).toBe("\u200B\u200B\u200B\u200BAtlas - Plan Executor")
|
||||||
|
})
|
||||||
|
|
||||||
describe("#given atlas display name with zero-width prefix", () => {
|
describe("#given atlas display name with zero-width prefix", () => {
|
||||||
describe("#when checking registration without the zero-width prefix", () => {
|
describe("#when checking registration without the zero-width prefix", () => {
|
||||||
test("#then it treats the display name as registered", () => {
|
test("#then it treats the display name as registered", () => {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function getMainSessionID(): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const registeredAgentNames = new Set<string>()
|
const registeredAgentNames = new Set<string>()
|
||||||
|
const registeredAgentAliases = new Map<string, string>()
|
||||||
|
|
||||||
const ZERO_WIDTH_CHARACTERS_REGEX = /[\u200B\u200C\u200D\uFEFF]/g
|
const ZERO_WIDTH_CHARACTERS_REGEX = /[\u200B\u200C\u200D\uFEFF]/g
|
||||||
|
|
||||||
@@ -28,10 +29,16 @@ function normalizeStoredAgentName(name: string): string {
|
|||||||
export function registerAgentName(name: string): void {
|
export function registerAgentName(name: string): void {
|
||||||
const normalizedName = normalizeRegisteredAgentName(name)
|
const normalizedName = normalizeRegisteredAgentName(name)
|
||||||
registeredAgentNames.add(normalizedName)
|
registeredAgentNames.add(normalizedName)
|
||||||
|
if (!registeredAgentAliases.has(normalizedName)) {
|
||||||
|
registeredAgentAliases.set(normalizedName, name)
|
||||||
|
}
|
||||||
|
|
||||||
const configKey = normalizeRegisteredAgentName(getAgentConfigKey(name))
|
const configKey = normalizeRegisteredAgentName(getAgentConfigKey(name))
|
||||||
if (configKey !== normalizedName) {
|
if (configKey !== normalizedName) {
|
||||||
registeredAgentNames.add(configKey)
|
registeredAgentNames.add(configKey)
|
||||||
|
if (!registeredAgentAliases.has(configKey)) {
|
||||||
|
registeredAgentAliases.set(configKey, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +46,15 @@ export function isAgentRegistered(name: string): boolean {
|
|||||||
return registeredAgentNames.has(normalizeRegisteredAgentName(name))
|
return registeredAgentNames.has(normalizeRegisteredAgentName(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveRegisteredAgentName(name: string | undefined): string | undefined {
|
||||||
|
if (typeof name !== "string") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedName = normalizeRegisteredAgentName(name)
|
||||||
|
return registeredAgentAliases.get(normalizedName) ?? normalizeStoredAgentName(name)
|
||||||
|
}
|
||||||
|
|
||||||
/** @internal For testing only */
|
/** @internal For testing only */
|
||||||
export function _resetForTesting(): void {
|
export function _resetForTesting(): void {
|
||||||
_mainSessionID = undefined
|
_mainSessionID = undefined
|
||||||
@@ -46,6 +62,7 @@ export function _resetForTesting(): void {
|
|||||||
syncSubagentSessions.clear()
|
syncSubagentSessions.clear()
|
||||||
sessionAgentMap.clear()
|
sessionAgentMap.clear()
|
||||||
registeredAgentNames.clear()
|
registeredAgentNames.clear()
|
||||||
|
registeredAgentAliases.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionAgentMap = new Map<string, string>()
|
const sessionAgentMap = new Map<string, string>()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import type { BackgroundManager } from "../../features/background-agent"
|
import type { BackgroundManager } from "../../features/background-agent"
|
||||||
import { isAgentRegistered } from "../../features/claude-code-session-state"
|
import {
|
||||||
|
isAgentRegistered,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
|
} from "../../features/claude-code-session-state"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
||||||
import { HOOK_NAME } from "./hook-name"
|
import { HOOK_NAME } from "./hook-name"
|
||||||
@@ -55,7 +58,9 @@ export async function injectBoulderContinuation(input: {
|
|||||||
`\n\n[Status: ${total - remaining}/${total} completed, ${remaining} remaining]` +
|
`\n\n[Status: ${total - remaining}/${total} completed, ${remaining} remaining]` +
|
||||||
preferredSessionContext +
|
preferredSessionContext +
|
||||||
worktreeContext
|
worktreeContext
|
||||||
const continuationAgent = (agent ?? (isAgentRegistered("atlas") ? "atlas" : undefined))?.replace(/\u200B/g, "")
|
const continuationAgent = resolveRegisteredAgentName(
|
||||||
|
agent ?? (isAgentRegistered("atlas") ? "atlas" : undefined),
|
||||||
|
)
|
||||||
|
|
||||||
if (!continuationAgent || !isAgentRegistered(continuationAgent)) {
|
if (!continuationAgent || !isAgentRegistered(continuationAgent)) {
|
||||||
log(`[${HOOK_NAME}] Skipped injection: continuation agent unavailable`, {
|
log(`[${HOOK_NAME}] Skipped injection: continuation agent unavailable`, {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { updateSessionAgent } from "../../features/claude-code-session-state"
|
import {
|
||||||
|
resolveRegisteredAgentName,
|
||||||
|
updateSessionAgent,
|
||||||
|
} from "../../features/claude-code-session-state"
|
||||||
import {
|
import {
|
||||||
getCompactionAgentConfigCheckpoint,
|
getCompactionAgentConfigCheckpoint,
|
||||||
} from "../../shared/compaction-agent-config-checkpoint"
|
} from "../../shared/compaction-agent-config-checkpoint"
|
||||||
@@ -66,6 +69,7 @@ export function createRecoveryLogic(
|
|||||||
checkpointWithAgent,
|
checkpointWithAgent,
|
||||||
currentPromptConfig,
|
currentPromptConfig,
|
||||||
)
|
)
|
||||||
|
const launchAgent = resolveRegisteredAgentName(expectedPromptConfig.agent)
|
||||||
const model = expectedPromptConfig.model
|
const model = expectedPromptConfig.model
|
||||||
const tools = expectedPromptConfig.tools
|
const tools = expectedPromptConfig.tools
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ export function createRecoveryLogic(
|
|||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
noReply: true,
|
noReply: true,
|
||||||
agent: expectedPromptConfig.agent,
|
agent: launchAgent ?? expectedPromptConfig.agent,
|
||||||
...(model ? { model } : {}),
|
...(model ? { model } : {}),
|
||||||
...(tools ? { tools } : {}),
|
...(tools ? { tools } : {}),
|
||||||
parts: [createInternalAgentTextPart(AGENT_RECOVERY_PROMPT)],
|
parts: [createInternalAgentTextPart(AGENT_RECOVERY_PROMPT)],
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import { isGptModel } from "../../agents/types"
|
import { isGptModel } from "../../agents/types"
|
||||||
import { getSessionAgent, updateSessionAgent } from "../../features/claude-code-session-state"
|
import {
|
||||||
|
getSessionAgent,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
|
updateSessionAgent,
|
||||||
|
} from "../../features/claude-code-session-state"
|
||||||
import { log } from "../../shared"
|
import { log } from "../../shared"
|
||||||
import { getAgentConfigKey, getAgentDisplayName } from "../../shared/agent-display-names"
|
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||||
|
|
||||||
const TOAST_TITLE = "NEVER Use Hephaestus with Non-GPT"
|
const TOAST_TITLE = "NEVER Use Hephaestus with Non-GPT"
|
||||||
const TOAST_MESSAGE = [
|
const TOAST_MESSAGE = [
|
||||||
@@ -10,8 +14,6 @@ const TOAST_MESSAGE = [
|
|||||||
"Hephaestus is trash without GPT.",
|
"Hephaestus is trash without GPT.",
|
||||||
"For Claude/Kimi/GLM models, always use Sisyphus.",
|
"For Claude/Kimi/GLM models, always use Sisyphus.",
|
||||||
].join("\n")
|
].join("\n")
|
||||||
const SISYPHUS_DISPLAY = getAgentDisplayName("sisyphus")
|
|
||||||
|
|
||||||
type NoHephaestusNonGptHookOptions = {
|
type NoHephaestusNonGptHookOptions = {
|
||||||
allowNonGptModel?: boolean
|
allowNonGptModel?: boolean
|
||||||
}
|
}
|
||||||
@@ -54,9 +56,9 @@ export function createNoHephaestusNonGptHook(
|
|||||||
if (allowNonGptModel) {
|
if (allowNonGptModel) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
input.agent = "sisyphus"
|
input.agent = resolveRegisteredAgentName("sisyphus") ?? "sisyphus"
|
||||||
if (output?.message) {
|
if (output?.message) {
|
||||||
output.message.agent = "sisyphus"
|
output.message.agent = resolveRegisteredAgentName("sisyphus") ?? "sisyphus"
|
||||||
}
|
}
|
||||||
updateSessionAgent(input.sessionID, "sisyphus")
|
updateSessionAgent(input.sessionID, "sisyphus")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import { isGptModel, isGpt5_4Model } from "../../agents/types"
|
import { isGptModel, isGpt5_4Model } from "../../agents/types"
|
||||||
import { getSessionAgent, updateSessionAgent } from "../../features/claude-code-session-state"
|
import {
|
||||||
|
getSessionAgent,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
|
updateSessionAgent,
|
||||||
|
} from "../../features/claude-code-session-state"
|
||||||
import { log } from "../../shared"
|
import { log } from "../../shared"
|
||||||
import { getAgentConfigKey, getAgentDisplayName } from "../../shared/agent-display-names"
|
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||||
|
|
||||||
const TOAST_TITLE = "NEVER Use Sisyphus with GPT"
|
const TOAST_TITLE = "NEVER Use Sisyphus with GPT"
|
||||||
const TOAST_MESSAGE = [
|
const TOAST_MESSAGE = [
|
||||||
@@ -10,8 +14,6 @@ const TOAST_MESSAGE = [
|
|||||||
"Do NOT use Sisyphus with GPT (except GPT-5.4 which has specialized support).",
|
"Do NOT use Sisyphus with GPT (except GPT-5.4 which has specialized support).",
|
||||||
"For GPT models (other than 5.4), always use Hephaestus.",
|
"For GPT models (other than 5.4), always use Hephaestus.",
|
||||||
].join("\n")
|
].join("\n")
|
||||||
const HEPHAESTUS_DISPLAY = getAgentDisplayName("hephaestus")
|
|
||||||
|
|
||||||
function showToast(ctx: PluginInput, sessionID: string): void {
|
function showToast(ctx: PluginInput, sessionID: string): void {
|
||||||
ctx.client.tui.showToast({
|
ctx.client.tui.showToast({
|
||||||
body: {
|
body: {
|
||||||
@@ -43,9 +45,9 @@ export function createNoSisyphusGptHook(ctx: PluginInput) {
|
|||||||
|
|
||||||
if (agentKey === "sisyphus" && modelID && isGptModel(modelID) && !isGpt5_4Model(modelID)) {
|
if (agentKey === "sisyphus" && modelID && isGptModel(modelID) && !isGpt5_4Model(modelID)) {
|
||||||
showToast(ctx, input.sessionID)
|
showToast(ctx, input.sessionID)
|
||||||
input.agent = "hephaestus"
|
input.agent = resolveRegisteredAgentName("hephaestus") ?? "hephaestus"
|
||||||
if (output?.message) {
|
if (output?.message) {
|
||||||
output.message.agent = "hephaestus"
|
output.message.agent = resolveRegisteredAgentName("hephaestus") ?? "hephaestus"
|
||||||
}
|
}
|
||||||
updateSessionAgent(input.sessionID, "hephaestus")
|
updateSessionAgent(input.sessionID, "hephaestus")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
|||||||
import { buildRetryModelPayload } from "./retry-model-payload"
|
import { buildRetryModelPayload } from "./retry-model-payload"
|
||||||
import { getLastUserRetryParts } from "./last-user-retry-parts"
|
import { getLastUserRetryParts } from "./last-user-retry-parts"
|
||||||
import { extractSessionMessages } from "./session-messages"
|
import { extractSessionMessages } from "./session-messages"
|
||||||
import { getAgentDisplayName } from "../../shared/agent-display-names"
|
import { resolveRegisteredAgentName } from "../../features/claude-code-session-state"
|
||||||
|
|
||||||
const SESSION_TTL_MS = 30 * 60 * 1000
|
const SESSION_TTL_MS = 30 * 60 * 1000
|
||||||
|
|
||||||
@@ -133,14 +133,14 @@ export function createAutoRetryHelpers(deps: HookDeps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const retryAgent = resolvedAgent ?? getSessionAgent(sessionID)
|
const retryAgent = resolvedAgent ?? getSessionAgent(sessionID)
|
||||||
|
const launchAgent = resolveRegisteredAgentName(retryAgent)
|
||||||
sessionAwaitingFallbackResult.add(sessionID)
|
sessionAwaitingFallbackResult.add(sessionID)
|
||||||
scheduleSessionFallbackTimeout(sessionID, retryAgent)
|
scheduleSessionFallbackTimeout(sessionID, retryAgent)
|
||||||
|
|
||||||
await ctx.client.session.promptAsync({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
// Use config key to avoid HTTP header validation issues with display names
|
...(launchAgent ? { agent: launchAgent } : {}),
|
||||||
...(retryAgent ? { agent: retryAgent } : {}),
|
|
||||||
...retryModelPayload,
|
...retryModelPayload,
|
||||||
parts: retryParts,
|
parts: retryParts,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import {
|
import {
|
||||||
isAgentRegistered,
|
isAgentRegistered,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
updateSessionAgent,
|
updateSessionAgent,
|
||||||
} from "../../features/claude-code-session-state"
|
} from "../../features/claude-code-session-state"
|
||||||
import { detectWorktreePath } from "./worktree-detector"
|
import { detectWorktreePath } from "./worktree-detector"
|
||||||
@@ -83,7 +84,7 @@ export function createStartWorkHook(ctx: PluginInput) {
|
|||||||
: "sisyphus"
|
: "sisyphus"
|
||||||
updateSessionAgent(input.sessionID, activeAgent)
|
updateSessionAgent(input.sessionID, activeAgent)
|
||||||
if (output.message) {
|
if (output.message) {
|
||||||
output.message["agent"] = activeAgent
|
output.message["agent"] = resolveRegisteredAgentName(activeAgent) ?? activeAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingState = readBoulderState(ctx.directory)
|
const existingState = readBoulderState(ctx.directory)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { injectContinuation } from "./continuation-injection"
|
|||||||
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
|
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
|
||||||
|
|
||||||
describe("injectContinuation", () => {
|
describe("injectContinuation", () => {
|
||||||
test("normalizes built-in display names to config keys before promptAsync", async () => {
|
test("preserves the registered built-in agent name before promptAsync", async () => {
|
||||||
// given
|
// given
|
||||||
let capturedAgent: string | undefined
|
let capturedAgent: string | undefined
|
||||||
const ctx = {
|
const ctx = {
|
||||||
@@ -40,7 +40,7 @@ describe("injectContinuation", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(capturedAgent).toBe("sisyphus")
|
expect(capturedAgent).toBe("Sisyphus - Ultraworker")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("inherits tools from resolved message info when reinjecting", async () => {
|
test("inherits tools from resolved message info when reinjecting", async () => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
|
|
||||||
import type { BackgroundManager } from "../../features/background-agent"
|
import type { BackgroundManager } from "../../features/background-agent"
|
||||||
import { getSessionAgent } from "../../features/claude-code-session-state"
|
import {
|
||||||
|
getSessionAgent,
|
||||||
|
resolveRegisteredAgentName,
|
||||||
|
} from "../../features/claude-code-session-state"
|
||||||
import {
|
import {
|
||||||
createInternalAgentTextPart,
|
createInternalAgentTextPart,
|
||||||
normalizeSDKResponse,
|
normalizeSDKResponse,
|
||||||
@@ -127,6 +130,7 @@ export async function injectContinuation(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const promptAgent = normalizeAgentForPromptKey(agentName)
|
const promptAgent = normalizeAgentForPromptKey(agentName)
|
||||||
|
const launchAgent = resolveRegisteredAgentName(agentName)
|
||||||
|
|
||||||
if (promptAgent && skipAgents.some(s => getAgentConfigKey(s) === getAgentConfigKey(promptAgent))) {
|
if (promptAgent && skipAgents.some(s => getAgentConfigKey(s) === getAgentConfigKey(promptAgent))) {
|
||||||
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName })
|
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName })
|
||||||
@@ -168,7 +172,7 @@ ${todoList}`
|
|||||||
try {
|
try {
|
||||||
log(`[${HOOK_NAME}] Injecting continuation`, {
|
log(`[${HOOK_NAME}] Injecting continuation`, {
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: promptAgent,
|
agent: launchAgent ?? promptAgent,
|
||||||
model,
|
model,
|
||||||
incompleteCount: freshIncompleteCount,
|
incompleteCount: freshIncompleteCount,
|
||||||
})
|
})
|
||||||
@@ -183,7 +187,7 @@ ${todoList}`
|
|||||||
await ctx.client.session.promptAsync({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: promptAgent,
|
agent: launchAgent ?? promptAgent,
|
||||||
...(launchModel ? { model: launchModel } : {}),
|
...(launchModel ? { model: launchModel } : {}),
|
||||||
...(launchVariant ? { variant: launchVariant } : {}),
|
...(launchVariant ? { variant: launchVariant } : {}),
|
||||||
...(inheritedTools ? { tools: inheritedTools } : {}),
|
...(inheritedTools ? { tools: inheritedTools } : {}),
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import * as skillLoader from "../features/opencode-skill-loader";
|
|||||||
import type { OhMyOpenCodeConfig } from "../config";
|
import type { OhMyOpenCodeConfig } from "../config";
|
||||||
import type { PluginComponents } from "./plugin-components-loader";
|
import type { PluginComponents } from "./plugin-components-loader";
|
||||||
import { applyCommandConfig } from "./command-config-handler";
|
import { applyCommandConfig } from "./command-config-handler";
|
||||||
import { getAgentDisplayName } from "../shared/agent-display-names";
|
import {
|
||||||
|
getAgentDisplayName,
|
||||||
|
getAgentListDisplayName,
|
||||||
|
} from "../shared/agent-display-names";
|
||||||
|
|
||||||
function createPluginComponents(): PluginComponents {
|
function createPluginComponents(): PluginComponents {
|
||||||
return {
|
return {
|
||||||
@@ -97,7 +100,7 @@ describe("applyCommandConfig", () => {
|
|||||||
expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill");
|
expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("normalizes Atlas command agents to the canonical display name used for native routing", async () => {
|
test("normalizes Atlas command agents to the exported list key used by opencode command routing", async () => {
|
||||||
// given
|
// given
|
||||||
loadBuiltinCommandsSpy.mockReturnValue({
|
loadBuiltinCommandsSpy.mockReturnValue({
|
||||||
"start-work": {
|
"start-work": {
|
||||||
@@ -119,10 +122,10 @@ describe("applyCommandConfig", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
const commandConfig = config.command as Record<string, { agent?: string }>;
|
const commandConfig = config.command as Record<string, { agent?: string }>;
|
||||||
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"));
|
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("normalizes legacy display-name command agents to the canonical display name", async () => {
|
test("normalizes legacy display-name command agents to the exported list key", async () => {
|
||||||
// given
|
// given
|
||||||
loadBuiltinCommandsSpy.mockReturnValue({
|
loadBuiltinCommandsSpy.mockReturnValue({
|
||||||
"start-work": {
|
"start-work": {
|
||||||
@@ -144,6 +147,6 @@ describe("applyCommandConfig", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
const commandConfig = config.command as Record<string, { agent?: string }>;
|
const commandConfig = config.command as Record<string, { agent?: string }>;
|
||||||
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"));
|
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { OhMyOpenCodeConfig } from "../config";
|
import type { OhMyOpenCodeConfig } from "../config";
|
||||||
import {
|
import {
|
||||||
getAgentConfigKey,
|
getAgentConfigKey,
|
||||||
getAgentDisplayName,
|
getAgentListDisplayName,
|
||||||
} from "../shared/agent-display-names";
|
} from "../shared/agent-display-names";
|
||||||
import {
|
import {
|
||||||
loadUserCommands,
|
loadUserCommands,
|
||||||
@@ -99,7 +99,7 @@ export async function applyCommandConfig(params: {
|
|||||||
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
|
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
|
||||||
for (const cmd of Object.values(commands)) {
|
for (const cmd of Object.values(commands)) {
|
||||||
if (cmd?.agent && typeof cmd.agent === "string") {
|
if (cmd?.agent && typeof cmd.agent === "string") {
|
||||||
cmd.agent = getAgentDisplayName(getAgentConfigKey(cmd.agent));
|
cmd.agent = getAgentListDisplayName(getAgentConfigKey(cmd.agent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1251,7 +1251,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("command agent routing coherence", () => {
|
describe("command agent routing coherence", () => {
|
||||||
test("keeps start-work aligned with the canonical Atlas display name", async () => {
|
test("keeps start-work aligned with the exported Atlas list key opencode matches exactly", async () => {
|
||||||
//#given
|
//#given
|
||||||
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
|
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
|
||||||
mockResolvedValue: (value: Record<string, unknown>) => void
|
mockResolvedValue: (value: Record<string, unknown>) => void
|
||||||
@@ -1291,7 +1291,7 @@ describe("command agent routing coherence", () => {
|
|||||||
const agentConfig = config.agent as Record<string, unknown>
|
const agentConfig = config.agent as Record<string, unknown>
|
||||||
const commandConfig = config.command as Record<string, { agent?: string }>
|
const commandConfig = config.command as Record<string, { agent?: string }>
|
||||||
expect(Object.keys(agentConfig)).toContain(getAgentListDisplayName("atlas"))
|
expect(Object.keys(agentConfig)).toContain(getAgentListDisplayName("atlas"))
|
||||||
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"))
|
expect(commandConfig["start-work"]?.agent).toBe(getAgentListDisplayName("atlas"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user