fix(delegate-task): reject stray backend-style categories

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-31 13:05:44 -07:00
parent 68ae9dca4b
commit 1d0135b230
6 changed files with 103 additions and 4 deletions
@@ -0,0 +1,14 @@
declare const require: (name: string) => any
const { describe, expect, test } = require("bun:test")
import { PROMETHEUS_GPT_SYSTEM_PROMPT } from "./prometheus/gpt"
describe("PROMETHEUS_GPT_SYSTEM_PROMPT category guidance", () => {
test("#given recommended agent profile instructions #when reading category placeholder #then it must point planners at available categories rather than a free-form name", () => {
//#given
const prompt = PROMETHEUS_GPT_SYSTEM_PROMPT
//#when / #then
expect(prompt).not.toContain("Category: `[name]`")
expect(prompt).toContain("Category: `[category-from-available-categories-above]`")
})
})
+1 -1
View File
@@ -363,7 +363,7 @@ Wave 2: [dependent tasks with categories]
**Must NOT do**: [specific exclusions]
**Recommended Agent Profile**:
- Category: \`[name]\` — Reason: [why]
- Category: \`[category-from-available-categories-above]\` — Reason: [why]
- Skills: [\`skill-1\`] — [why needed]
- Omitted: [\`skill-x\`] — [why not needed]
@@ -0,0 +1,43 @@
declare const require: (name: string) => any
const { afterEach, beforeEach, describe, expect, mock, spyOn, test } = require("bun:test")
import { resolveCategoryExecution } from "./category-resolver"
import type { ExecutorContext } from "./executor-types"
import * as availableModels from "./available-models"
describe("resolveCategoryExecution unknown category handling", () => {
beforeEach(() => {
mock.restore()
})
afterEach(() => {
mock.restore()
})
test("#given unknown category #when resolving category execution #then it rejects before fetching available models", async () => {
//#given
const availableModelsSpy = spyOn(availableModels, "getAvailableModelsForDelegateTask")
const executorContext: ExecutorContext = {
client: {} as ExecutorContext["client"],
manager: {} as ExecutorContext["manager"],
directory: "/tmp/test",
userCategories: {},
sisyphusJuniorModel: undefined,
}
const args = {
category: "backend-engineer",
prompt: "test prompt",
description: "Test task",
run_in_background: false,
load_skills: [],
blockedBy: undefined,
enableSkillTools: false,
}
//#when
const result = await resolveCategoryExecution(args, executorContext, undefined, "anthropic/claude-sonnet-4-6")
//#then
expect(result.error).toContain('Unknown category: "backend-engineer"')
expect(availableModelsSpy).not.toHaveBeenCalled()
})
})
+16 -2
View File
@@ -45,12 +45,26 @@ export async function resolveCategoryExecution(
): Promise<CategoryResolutionResult> {
const { client, userCategories, sisyphusJuniorModel } = executorCtx
const availableModels = await getAvailableModelsForDelegateTask(client)
const categoryName = args.category!
const enabledCategories = mergeCategories(userCategories)
const categoryExists = enabledCategories[categoryName] !== undefined
if (!categoryExists) {
const allCategoryNames = Object.keys(enabledCategories).join(", ")
return {
agentToUse: "",
categoryModel: undefined,
categoryPromptAppend: undefined,
maxPromptTokens: undefined,
modelInfo: undefined,
actualModel: undefined,
isUnstableAgent: false,
error: `Unknown category: "${categoryName}". Available: ${allCategoryNames}`,
}
}
const availableModels = await getAvailableModelsForDelegateTask(client)
const resolved = resolveCategoryConfig(categoryName, {
userCategories,
inheritedModel,
@@ -0,0 +1,28 @@
declare const require: (name: string) => any
const { describe, expect, test } = require("bun:test")
import { createDelegateTask } from "./tools"
describe("createDelegateTask schema", () => {
test("#given category arg #when tool is created #then category is constrained to available enum values", () => {
//#given
const toolDefinition = createDelegateTask({ manager: {} as never, client: {} as never, directory: "/tmp/test" })
//#when
const categorySchema = toolDefinition.args.category as unknown as {
def: {
type: string
innerType: {
def: { type: string }
options: string[]
}
}
}
//#then
expect(categorySchema.def.type).toBe("optional")
expect(categorySchema.def.innerType.def.type).toBe("enum")
expect(categorySchema.def.innerType.options).toContain("quick")
expect(categorySchema.def.innerType.options).toContain("deep")
expect(categorySchema.def.innerType.options).toContain("ultrabrain")
})
})
+1 -1
View File
@@ -101,7 +101,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
description: tool.schema.string().describe("Short task description (3-5 words)"),
prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."),
category: tool.schema.string().optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`),
category: tool.schema.enum(categoryNames).optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`),
subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type. Must be a callable non-primary agent name returned by app.agents()."),
session_id: tool.schema.string().optional().describe("Existing Task session to continue"),
command: tool.schema.string().optional().describe("The command that triggered this task"),