Fix invisible characters in agent names
This commit is contained in:
@@ -186,6 +186,11 @@ describe("getAgentConfigKey", () => {
|
|||||||
it("resolves atlas even when the UI ordering prefix is present", () => {
|
it("resolves atlas even when the UI ordering prefix is present", () => {
|
||||||
expect(getAgentConfigKey(getAgentListDisplayName("atlas"))).toBe("atlas")
|
expect(getAgentConfigKey(getAgentListDisplayName("atlas"))).toBe("atlas")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("resolves display names even when zero-width characters are embedded", () => {
|
||||||
|
expect(getAgentConfigKey("Sisyphus\u200B - Ultraworker")).toBe("sisyphus")
|
||||||
|
expect(getAgentConfigKey("\uFEFFAtlas - Plan Executor")).toBe("atlas")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("getAgentListDisplayName", () => {
|
describe("getAgentListDisplayName", () => {
|
||||||
@@ -208,6 +213,10 @@ describe("normalizeAgentForPrompt", () => {
|
|||||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("prometheus"))).toBe("Prometheus - Plan Builder")
|
expect(normalizeAgentForPrompt(getAgentListDisplayName("prometheus"))).toBe("Prometheus - Plan Builder")
|
||||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("atlas"))).toBe("Atlas - Plan Executor")
|
expect(normalizeAgentForPrompt(getAgentListDisplayName("atlas"))).toBe("Atlas - Plan Executor")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("removes zero-width characters before returning canonical names", () => {
|
||||||
|
expect(normalizeAgentForPrompt("Sisyphus\u200B - Ultraworker")).toBe("Sisyphus - Ultraworker")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("normalizeAgentForPromptKey", () => {
|
describe("normalizeAgentForPromptKey", () => {
|
||||||
|
|||||||
@@ -33,8 +33,14 @@ const AGENT_LIST_SORT_PREFIXES: Record<string, string> = {
|
|||||||
atlas: "\u200B\u200B\u200B\u200B",
|
atlas: "\u200B\u200B\u200B\u200B",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ZERO_WIDTH_AGENT_CHARACTERS_REGEX = /[\u200B\u200C\u200D\uFEFF]/g
|
||||||
|
|
||||||
|
export function stripInvisibleAgentCharacters(agentName: string): string {
|
||||||
|
return agentName.replace(ZERO_WIDTH_AGENT_CHARACTERS_REGEX, "")
|
||||||
|
}
|
||||||
|
|
||||||
export function stripAgentListSortPrefix(agentName: string): string {
|
export function stripAgentListSortPrefix(agentName: string): string {
|
||||||
return agentName.replace(/^\u200B+/, "")
|
return stripInvisibleAgentCharacters(agentName)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAgentRuntimeName(configKey: string): string {
|
export function getAgentRuntimeName(configKey: string): string {
|
||||||
@@ -97,7 +103,7 @@ const LEGACY_DISPLAY_NAMES: Record<string, string> = {
|
|||||||
* "Atlas - Plan Executor" -> "atlas", "Atlas (Plan Executor)" -> "atlas", "atlas" -> "atlas"
|
* "Atlas - Plan Executor" -> "atlas", "Atlas (Plan Executor)" -> "atlas", "atlas" -> "atlas"
|
||||||
*/
|
*/
|
||||||
export function getAgentConfigKey(agentName: string): string {
|
export function getAgentConfigKey(agentName: string): string {
|
||||||
const lower = stripAgentListSortPrefix(agentName).toLowerCase()
|
const lower = stripAgentListSortPrefix(agentName).trim().toLowerCase()
|
||||||
const reversed = REVERSE_DISPLAY_NAMES[lower]
|
const reversed = REVERSE_DISPLAY_NAMES[lower]
|
||||||
if (reversed !== undefined) return reversed
|
if (reversed !== undefined) return reversed
|
||||||
const legacy = LEGACY_DISPLAY_NAMES[lower]
|
const legacy = LEGACY_DISPLAY_NAMES[lower]
|
||||||
@@ -117,7 +123,7 @@ export function normalizeAgentForPrompt(agentName: string | undefined): string |
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = stripAgentListSortPrefix(agentName.trim())
|
const trimmed = stripAgentListSortPrefix(agentName).trim()
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -143,7 +149,7 @@ export function normalizeAgentForPromptKey(agentName: string | undefined): strin
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = stripAgentListSortPrefix(agentName.trim())
|
const trimmed = stripAgentListSortPrefix(agentName).trim()
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,27 @@ describe("executeSync", () => {
|
|||||||
expect(promptInput?.body.parts).toEqual([{ type: "text", text: "find something" }])
|
expect(promptInput?.body.parts).toEqual([{ type: "text", text: "find something" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("removes invisible agent characters before sending the sync prompt", async () => {
|
||||||
|
//#given
|
||||||
|
const executeSync = await importExecuteSync()
|
||||||
|
const deps = createDependencies()
|
||||||
|
const toolContext = createToolContext()
|
||||||
|
const recorder = createPromptAsyncRecorder()
|
||||||
|
const args = {
|
||||||
|
subagent_type: "\u200BSisyphus\u200B - Ultraworker",
|
||||||
|
description: "test task",
|
||||||
|
prompt: "find something",
|
||||||
|
run_in_background: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
const promptInput = recorder.getCapturedInput()
|
||||||
|
expect(promptInput?.body.agent).toBe("Sisyphus - Ultraworker")
|
||||||
|
})
|
||||||
|
|
||||||
test("returns processed response with task metadata footer", async () => {
|
test("returns processed response with task metadata footer", async () => {
|
||||||
//#given
|
//#given
|
||||||
const executeSync = await importExecuteSync()
|
const executeSync = await importExecuteSync()
|
||||||
|
|||||||
@@ -163,6 +163,21 @@ describe("resolveSubagentExecution", () => {
|
|||||||
expect(result.categoryModel).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" })
|
expect(result.categoryModel).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("matches agents even when zero-width characters are present in the requested name", async () => {
|
||||||
|
//#given
|
||||||
|
const args = createBaseArgs({ subagent_type: "\uFEFFSisyphus - Ultraworker" })
|
||||||
|
const executorCtx = createExecutorContext(async () => ([
|
||||||
|
{ name: "\u200BSisyphus - Ultraworker", mode: "subagent", model: "openai/gpt-5.3-codex" },
|
||||||
|
]))
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = await resolveSubagentExecution(args, executorCtx, "oracle", "deep")
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result.error).toBeUndefined()
|
||||||
|
expect(result.agentToUse).toBe("Sisyphus - Ultraworker")
|
||||||
|
})
|
||||||
|
|
||||||
test("uses agent override fallback_models for subagent runtime fallback chain", async () => {
|
test("uses agent override fallback_models for subagent runtime fallback chain", async () => {
|
||||||
//#given
|
//#given
|
||||||
readProviderModelsCacheMock.mockReturnValue({
|
readProviderModelsCacheMock.mockReturnValue({
|
||||||
|
|||||||
Reference in New Issue
Block a user