Files
oh-my-opencode/src/tools/delegate-task/tools.test.ts
T
YeonGyu-Kim 0ef682965f fix: detect interrupted/error/cancelled status in unstable-agent-task polling loop
The polling loop in executeUnstableAgentTask only checked session status
and message stability, never checking if the background task itself had
been interrupted. This caused the tool call to hang until MAX_POLL_TIME_MS
(10 minutes) when a task was interrupted by prompt errors.

Add manager.getTask() check at each poll iteration to break immediately
on terminal statuses (interrupt, error, cancelled), returning a clear
failure message instead of hanging.
2026-02-16 15:56:52 +09:00

3828 lines
129 KiB
TypeScript

declare const require: (name: string) => any
const { describe, test, expect, beforeEach, afterEach, spyOn, mock } = require("bun:test")
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, isPlanAgent, PLAN_AGENT_NAMES, isPlanFamily, PLAN_FAMILY_NAMES } from "./constants"
import { resolveCategoryConfig } from "./tools"
import type { CategoryConfig } from "../../config/schema"
import type { DelegateTaskArgs } from "./types"
import { __resetModelCache } from "../../shared/model-availability"
import { clearSkillCache } from "../../features/opencode-skill-loader/skill-content"
import { __setTimingConfig, __resetTimingConfig } from "./timing"
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
import * as executor from "./executor"
const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-5"
const TEST_CONNECTED_PROVIDERS = ["anthropic", "google", "openai"]
const TEST_AVAILABLE_MODELS = new Set([
"anthropic/claude-opus-4-6",
"anthropic/claude-sonnet-4-5",
"anthropic/claude-haiku-4-5",
"google/gemini-3-pro",
"google/gemini-3-flash",
"openai/gpt-5.2",
"openai/gpt-5.3-codex",
])
type DelegateTaskArgsWithSerializedSkills = Omit<DelegateTaskArgs, "load_skills"> & {
load_skills: string
}
function createTestAvailableModels(): Set<string> {
return new Set(TEST_AVAILABLE_MODELS)
}
describe("sisyphus-task", () => {
let cacheSpy: ReturnType<typeof spyOn>
let providerModelsSpy: ReturnType<typeof spyOn>
beforeEach(() => {
mock.restore()
__resetModelCache()
clearSkillCache()
__setTimingConfig({
POLL_INTERVAL_MS: 10,
MIN_STABILITY_TIME_MS: 50,
STABILITY_POLLS_REQUIRED: 1,
WAIT_FOR_SESSION_INTERVAL_MS: 10,
WAIT_FOR_SESSION_TIMEOUT_MS: 1000,
MAX_POLL_TIME_MS: 2000,
SESSION_CONTINUATION_STABILITY_MS: 50,
})
cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic", "google", "openai"])
providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({
models: {
anthropic: ["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"],
google: ["gemini-3-pro", "gemini-3-flash"],
openai: ["gpt-5.2", "gpt-5.3-codex"],
},
connected: ["anthropic", "google", "openai"],
updatedAt: "2026-01-01T00:00:00.000Z",
})
})
afterEach(() => {
__resetTimingConfig()
cacheSpy?.mockRestore()
providerModelsSpy?.mockRestore()
})
describe("DEFAULT_CATEGORIES", () => {
test("visual-engineering category has model and variant config", () => {
// given
const category = DEFAULT_CATEGORIES["visual-engineering"]
// when / #then
expect(category).toBeDefined()
expect(category.model).toBe("google/gemini-3-pro")
expect(category.variant).toBe("high")
})
test("ultrabrain category has model and variant config", () => {
// given
const category = DEFAULT_CATEGORIES["ultrabrain"]
// when / #then
expect(category).toBeDefined()
expect(category.model).toBe("openai/gpt-5.3-codex")
expect(category.variant).toBe("xhigh")
})
test("deep category has model and variant config", () => {
// given
const category = DEFAULT_CATEGORIES["deep"]
// when / #then
expect(category).toBeDefined()
expect(category.model).toBe("openai/gpt-5.3-codex")
expect(category.variant).toBe("medium")
})
})
describe("CATEGORY_PROMPT_APPENDS", () => {
test("visual-engineering category has design-focused prompt", () => {
// given
const promptAppend = CATEGORY_PROMPT_APPENDS["visual-engineering"]
// when / #then
expect(promptAppend).toContain("VISUAL/UI")
expect(promptAppend).toContain("Design-first")
})
test("ultrabrain category has deep logical reasoning prompt", () => {
// given
const promptAppend = CATEGORY_PROMPT_APPENDS["ultrabrain"]
// when / #then
expect(promptAppend).toContain("DEEP LOGICAL REASONING")
expect(promptAppend).toContain("Strategic advisor")
})
test("deep category has goal-oriented autonomous prompt", () => {
// given
const promptAppend = CATEGORY_PROMPT_APPENDS["deep"]
// when / #then
expect(promptAppend).toContain("GOAL-ORIENTED")
expect(promptAppend).toContain("autonomous")
})
})
describe("CATEGORY_DESCRIPTIONS", () => {
test("has description for all default categories", () => {
// given
const defaultCategoryNames = Object.keys(DEFAULT_CATEGORIES)
// when / #then
for (const name of defaultCategoryNames) {
expect(CATEGORY_DESCRIPTIONS[name]).toBeDefined()
expect(CATEGORY_DESCRIPTIONS[name].length).toBeGreaterThan(0)
}
})
test("unspecified-high category exists and has description", () => {
// given / #when
const description = CATEGORY_DESCRIPTIONS["unspecified-high"]
// then
expect(description).toBeDefined()
expect(description).toContain("high effort")
})
})
describe("isPlanAgent", () => {
test("returns true for 'plan'", () => {
// given / #when
const result = isPlanAgent("plan")
// then
expect(result).toBe(true)
})
test("returns false for 'prometheus' (decoupled from plan)", () => {
//#given / #when
const result = isPlanAgent("prometheus")
//#then - prometheus is NOT a plan agent
expect(result).toBe(false)
})
test("returns true for 'planner' (matches via includes('plan'))", () => {
//#given / #when
const result = isPlanAgent("planner")
//#then - "planner" contains "plan" so it matches via includes
expect(result).toBe(true)
})
test("returns true for case-insensitive match 'PLAN'", () => {
// given / #when
const result = isPlanAgent("PLAN")
// then
expect(result).toBe(true)
})
test("returns false for case-insensitive match 'Prometheus' (decoupled from plan)", () => {
//#given / #when
const result = isPlanAgent("Prometheus")
//#then - Prometheus is NOT a plan agent
expect(result).toBe(false)
})
test("returns false for 'oracle'", () => {
// given / #when
const result = isPlanAgent("oracle")
// then
expect(result).toBe(false)
})
test("returns false for 'explore'", () => {
// given / #when
const result = isPlanAgent("explore")
// then
expect(result).toBe(false)
})
test("returns false for undefined", () => {
// given / #when
const result = isPlanAgent(undefined)
// then
expect(result).toBe(false)
})
test("returns false for empty string", () => {
// given / #when
const result = isPlanAgent("")
// then
expect(result).toBe(false)
})
test("PLAN_AGENT_NAMES contains only plan", () => {
//#given / #when / #then
expect(PLAN_AGENT_NAMES).toEqual(["plan"])
})
})
describe("isPlanFamily", () => {
test("returns true for 'plan'", () => {
//#given / #when
const result = isPlanFamily("plan")
//#then
expect(result).toBe(true)
})
test("returns true for 'prometheus'", () => {
//#given / #when
const result = isPlanFamily("prometheus")
//#then
expect(result).toBe(true)
})
test("returns false for 'oracle'", () => {
//#given / #when
const result = isPlanFamily("oracle")
//#then
expect(result).toBe(false)
})
test("returns false for undefined", () => {
//#given / #when
const result = isPlanFamily(undefined)
//#then
expect(result).toBe(false)
})
test("PLAN_FAMILY_NAMES contains plan and prometheus", () => {
//#given / #when / #then
expect(PLAN_FAMILY_NAMES).toEqual(["plan", "prometheus"])
})
})
describe("load_skills parsing", () => {
test("parses valid JSON string into array before validation", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({
id: "task-123",
status: "pending",
description: "Parse test",
agent: "sisyphus-junior",
sessionID: "test-session",
}),
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) },
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
const resolveSkillContentSpy = spyOn(executor, "resolveSkillContent").mockResolvedValue({
content: "resolved skill content",
error: null,
})
const args: DelegateTaskArgsWithSerializedSkills = {
description: "Parse valid string",
prompt: "Load skill parsing test",
category: "quick",
run_in_background: true,
load_skills: '["playwright", "git-master"]',
}
//#when
await tool.execute(args as unknown as DelegateTaskArgs, toolContext)
//#then
expect(args.load_skills).toEqual(["playwright", "git-master"])
expect(resolveSkillContentSpy).toHaveBeenCalledWith(["playwright", "git-master"], expect.any(Object))
}, { timeout: 10000 })
test("defaults to [] when load_skills is malformed JSON", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({
id: "task-456",
status: "pending",
description: "Parse test",
agent: "sisyphus-junior",
sessionID: "test-session",
}),
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) },
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
const resolveSkillContentSpy = spyOn(executor, "resolveSkillContent").mockResolvedValue({
content: "resolved skill content",
error: null,
})
const args: DelegateTaskArgsWithSerializedSkills = {
description: "Parse malformed string",
prompt: "Load skill parsing test",
category: "quick",
run_in_background: true,
load_skills: '["playwright", "git-master"',
}
//#when
await tool.execute(args as unknown as DelegateTaskArgs, toolContext)
//#then
expect(args.load_skills).toEqual([])
expect(resolveSkillContentSpy).toHaveBeenCalledWith([], expect.any(Object))
}, { timeout: 10000 })
})
describe("category delegation config validation", () => {
test("fills subagent_type as sisyphus-junior when category is provided without subagent_type", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({
id: "task-123",
status: "pending",
description: "Test task",
agent: "sisyphus-junior",
sessionID: "test-session",
}),
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) },
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
const args: {
description: string
prompt: string
category: string
run_in_background: boolean
load_skills: string[]
subagent_type?: string
} = {
description: "Quick category test",
prompt: "Do something",
category: "quick",
run_in_background: true,
load_skills: [],
}
// when
await tool.execute(args, toolContext)
// then
expect(args.subagent_type).toBe("sisyphus-junior")
}, { timeout: 10000 })
test("category overrides subagent_type and still maps to sisyphus-junior", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({
id: "task-override",
status: "pending",
description: "Override test",
agent: "sisyphus-junior",
sessionID: "test-session",
}),
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) },
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
const args: {
description: string
prompt: string
category: string
subagent_type: string
run_in_background: boolean
load_skills: string[]
} = {
description: "Override test",
prompt: "Do something",
category: "quick",
subagent_type: "oracle",
run_in_background: true,
load_skills: [],
}
//#when
const result = await tool.execute(args, toolContext)
//#then
expect(args.subagent_type).toBe("sisyphus-junior")
expect(result).toContain("Background task launched")
}, { timeout: 10000 })
test("proceeds without error when systemDefaultModel is undefined", async () => {
// given a mock client with no model in config
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({ id: "task-123", status: "pending", description: "Test task", agent: "sisyphus-junior", sessionID: "test-session" }) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) }, // No model configured
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when delegating with a category
const result = await tool.execute(
{
description: "Test task",
prompt: "Do something",
category: "ultrabrain",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then proceeds without error - uses fallback chain
expect(result).not.toContain("oh-my-opencode requires a default model")
}, { timeout: 10000 })
test("returns clear error when no model can be resolved", async () => {
// given - custom category with no model, no systemDefaultModel, no available models
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({ id: "task-123" }) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({}) }, // No model configured
model: { list: async () => [] }, // No available models
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
// Custom category with no model defined
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"custom-no-model": { temperature: 0.5 }, // No model field
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when delegating with a custom category that has no model
const result = await tool.execute(
{
description: "Test task",
prompt: "Do something",
category: "custom-no-model",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then returns clear error message with configuration guidance
expect(result).toContain("Model not configured")
expect(result).toContain("custom-no-model")
expect(result).toContain("Configure in one of")
})
})
describe("background metadata sessionId", () => {
test("should wait for background sessionId and set metadata for TUI toolcall counting", async () => {
//#given - manager.launch returns before sessionID is available
const { createDelegateTask } = require("./tools")
const tasks = new Map<string, { id: string; sessionID?: string; status: string; description: string; agent: string }>()
const mockManager = {
getTask: (id: string) => tasks.get(id),
launch: async () => {
const task = { id: "bg_1", status: "pending", description: "Test task", agent: "explore" }
tasks.set(task.id, task)
setTimeout(() => {
tasks.set(task.id, { ...task, status: "running", sessionID: "ses_child" })
}, 20)
return task
},
}
const mockClient = {
app: { agents: async () => ({ data: [{ name: "explore", mode: "subagent" }] }) },
config: { get: async () => ({}) },
provider: { list: async () => ({ data: { connected: ["openai"] } }) },
model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const metadataCalls: Array<{ title?: string; metadata?: Record<string, unknown> }> = []
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
metadata: (input: { title?: string; metadata?: Record<string, unknown> }) => {
metadataCalls.push(input)
},
}
const args = {
description: "Explore task",
prompt: "Explore features directory deeply",
subagent_type: "explore",
run_in_background: true,
load_skills: [],
}
//#when
const result = await tool.execute(args, toolContext)
//#then - metadata should include sessionId (camelCase) once it's available
expect(String(result)).toContain("Background task launched")
const sessionIdCall = metadataCalls.find((c) => c.metadata?.sessionId === "ses_child")
expect(sessionIdCall).toBeDefined()
})
})
describe("resolveCategoryConfig", () => {
test("returns null for unknown category without user config", () => {
// given
const categoryName = "unknown-category"
// when
const result = resolveCategoryConfig(categoryName, { systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).toBeNull()
})
test("blocks requiresModel when availability is known and missing the required model", () => {
// given
const categoryName = "deep"
const availableModels = new Set<string>(["anthropic/claude-opus-4-6"])
// when
const result = resolveCategoryConfig(categoryName, {
systemDefaultModel: SYSTEM_DEFAULT_MODEL,
availableModels,
})
// then
expect(result).toBeNull()
})
test("blocks requiresModel when availability is empty", () => {
// given
const categoryName = "deep"
const availableModels = new Set<string>()
// when
const result = resolveCategoryConfig(categoryName, {
systemDefaultModel: SYSTEM_DEFAULT_MODEL,
availableModels,
})
// then
expect(result).toBeNull()
})
test("bypasses requiresModel when explicit user config provided", () => {
// #given
const categoryName = "deep"
const availableModels = new Set<string>(["anthropic/claude-opus-4-6"])
const userCategories = {
deep: { model: "anthropic/claude-opus-4-6" },
}
// #when
const result = resolveCategoryConfig(categoryName, {
systemDefaultModel: SYSTEM_DEFAULT_MODEL,
availableModels,
userCategories,
})
// #then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
})
test("bypasses requiresModel when explicit user config provided even with empty availability", () => {
// #given
const categoryName = "deep"
const availableModels = new Set<string>()
const userCategories = {
deep: { model: "anthropic/claude-opus-4-6" },
}
// #when
const result = resolveCategoryConfig(categoryName, {
systemDefaultModel: SYSTEM_DEFAULT_MODEL,
availableModels,
userCategories,
})
// #then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
})
test("returns default model from DEFAULT_CATEGORIES for builtin category", () => {
// given
const categoryName = "visual-engineering"
// when
const result = resolveCategoryConfig(categoryName, { systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("google/gemini-3-pro")
expect(result!.promptAppend).toContain("VISUAL/UI")
})
test("user config overrides systemDefaultModel", () => {
// given
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering": { model: "anthropic/claude-opus-4-6" },
}
// when
const result = resolveCategoryConfig(categoryName, { userCategories, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("anthropic/claude-opus-4-6")
})
test("user prompt_append is appended to default", () => {
// given
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering": {
model: "google/gemini-3-pro",
prompt_append: "Custom instructions here",
},
}
// when
const result = resolveCategoryConfig(categoryName, { userCategories, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.promptAppend).toContain("VISUAL/UI")
expect(result!.promptAppend).toContain("Custom instructions here")
})
test("user can define custom category", () => {
// given
const categoryName = "my-custom"
const userCategories = {
"my-custom": {
model: "openai/gpt-5.2",
temperature: 0.5,
prompt_append: "You are a custom agent",
},
}
// when
const result = resolveCategoryConfig(categoryName, { userCategories, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("openai/gpt-5.2")
expect(result!.config.temperature).toBe(0.5)
expect(result!.promptAppend).toBe("You are a custom agent")
})
test("user category overrides temperature", () => {
// given
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering": {
model: "google/gemini-3-pro",
temperature: 0.3,
},
}
// when
const result = resolveCategoryConfig(categoryName, { userCategories, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.temperature).toBe(0.3)
})
test("category built-in model takes precedence over inheritedModel", () => {
// given - builtin category with its own model, parent model also provided
const categoryName = "visual-engineering"
const inheritedModel = "cliproxy/claude-opus-4-6"
// when
const result = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - category's built-in model wins over inheritedModel
expect(result).not.toBeNull()
expect(result!.config.model).toBe("google/gemini-3-pro")
})
test("systemDefaultModel is used as fallback when custom category has no model", () => {
// given - custom category with no model defined
const categoryName = "my-custom-no-model"
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
const inheritedModel = "cliproxy/claude-opus-4-6"
// when
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - systemDefaultModel is used since custom category has no built-in model
expect(result).not.toBeNull()
expect(result!.config.model).toBe(SYSTEM_DEFAULT_MODEL)
})
test("user model takes precedence over inheritedModel", () => {
// given
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering": { model: "my-provider/my-model" },
}
const inheritedModel = "cliproxy/claude-opus-4-6"
// when
const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("my-provider/my-model")
})
test("default model from category config is used when no user model and no inheritedModel", () => {
// given
const categoryName = "visual-engineering"
// when
const result = resolveCategoryConfig(categoryName, { systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then
expect(result).not.toBeNull()
expect(result!.config.model).toBe("google/gemini-3-pro")
})
})
describe("category variant", () => {
test("passes variant to background model payload", async () => {
// given
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-variant",
sessionID: "session-variant",
description: "Variant task",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
},
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
await tool.execute(
{
description: "Variant task",
prompt: "Do something",
category: "ultrabrain",
run_in_background: true,
load_skills: ["git-master"],
},
toolContext
)
// then
expect(launchInput.model).toEqual({
providerID: "openai",
modelID: "gpt-5.2",
variant: "xhigh",
})
})
test("DEFAULT_CATEGORIES variant passes to background WITHOUT userCategories", async () => {
// given - NO userCategories, testing DEFAULT_CATEGORIES only
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-default-variant",
sessionID: "session-default-variant",
description: "Default variant task",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
// NO userCategories - must use DEFAULT_CATEGORIES
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - unspecified-high has variant: "max" in DEFAULT_CATEGORIES
await tool.execute(
{
description: "Test unspecified-high default variant",
prompt: "Do something",
category: "unspecified-high",
run_in_background: true,
load_skills: ["git-master"],
},
toolContext
)
// then - variant MUST be "max" from DEFAULT_CATEGORIES
expect(launchInput.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
variant: "max",
})
})
test("DEFAULT_CATEGORIES variant passes to sync session.prompt WITHOUT userCategories", async () => {
// given - NO userCategories, testing DEFAULT_CATEGORIES for sync mode
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_sync_default_variant" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "done" }] }]
}),
status: async () => ({ data: { "ses_sync_default_variant": { type: "idle" } } }),
},
}
// NO userCategories - must use DEFAULT_CATEGORIES
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - unspecified-high has variant: "max" in DEFAULT_CATEGORIES
await tool.execute(
{
description: "Test unspecified-high sync variant",
prompt: "Do something",
category: "unspecified-high",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - variant MUST be "max" from DEFAULT_CATEGORIES (passed as separate field)
expect(promptBody.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
})
expect(promptBody.variant).toBe("max")
}, { timeout: 20000 })
})
describe("skills parameter", () => {
test("skills parameter is required - throws error when not provided", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - skills not provided (undefined)
// then - should throw error about missing skills
await expect(tool.execute(
{
description: "Test task",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
},
toolContext
)).rejects.toThrow("IT IS HIGHLY RECOMMENDED")
})
test("null skills throws error", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - null passed
// then - should throw error about null
await expect(tool.execute(
{
description: "Test task",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: null,
},
toolContext
)).rejects.toThrow("IT IS HIGHLY RECOMMENDED")
})
test("empty array [] is allowed and proceeds without skill content", async () => {
// given
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "test-session" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
}),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - empty array passed
await tool.execute(
{
description: "Test task",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - should proceed without system content from skills
expect(promptBody).toBeDefined()
}, { timeout: 20000 })
})
describe("session_id with background parameter", () => {
test("session_id with background=false should wait for result and return content", async () => {
// Note: This test needs extended timeout because the implementation has MIN_STABILITY_TIME_MS = 5000
// given
const { createDelegateTask } = require("./tools")
const mockTask = {
id: "task-123",
sessionID: "ses_continue_test",
description: "Continued task",
agent: "explore",
status: "running",
}
const mockManager = {
resume: async () => mockTask,
launch: async () => mockTask,
}
let messagesCallCount = 0
const mockClient = {
session: {
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => {
messagesCallCount++
const now = Date.now()
const beforeContinuation = [
{
info: { id: "msg_001", role: "user", time: { created: now } },
parts: [{ type: "text", text: "Previous context" }],
},
{
info: { id: "msg_002", role: "assistant", time: { created: now + 1 }, finish: "end_turn" },
parts: [{ type: "text", text: "Previous result" }],
},
]
if (messagesCallCount === 1) {
return { data: beforeContinuation }
}
return {
data: [
...beforeContinuation,
{
info: { id: "msg_003", role: "user", time: { created: now + 2 } },
parts: [{ type: "text", text: "Continue the task" }],
},
{
info: { id: "msg_004", role: "assistant", time: { created: now + 3 }, finish: "end_turn" },
parts: [{ type: "text", text: "This is the continued task result" }],
},
],
}
},
status: async () => ({ data: { "ses_continue_test": { type: "idle" } } }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Continue test",
prompt: "Continue the task",
session_id: "ses_continue_test",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should contain actual result, not just "Background task continued"
expect(result).toContain("This is the continued task result")
expect(result).not.toContain("Background task continued")
}, { timeout: 10000 })
test("sync continuation preserves variant from previous session message", async () => {
//#given a session with a previous message that has variant "max"
const { createDelegateTask } = require("./tools")
const promptMock = mock(async (input: any) => {
return { data: {} }
})
const mockClient = {
session: {
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [
{
info: {
id: "msg_001",
role: "user",
agent: "sisyphus-junior",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
variant: "max",
time: { created: Date.now() },
},
parts: [{ type: "text", text: "previous message" }],
},
{
info: { id: "msg_002", role: "assistant", time: { created: Date.now() + 1 }, finish: "end_turn" },
parts: [{ type: "text", text: "Completed." }],
},
],
}),
status: async () => ({ data: { "ses_var_test": { type: "idle" } } }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: { resume: async () => ({ id: "task-var", sessionID: "ses_var_test", description: "Variant test", agent: "sisyphus-junior", status: "running" }) },
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
//#when continuing the session
await tool.execute(
{
description: "Continue with variant",
prompt: "Continue the task",
session_id: "ses_var_test",
run_in_background: false,
load_skills: [],
},
toolContext
)
//#then prompt should include variant from previous message
expect(promptMock).toHaveBeenCalled()
const callArgs = promptMock.mock.calls[0][0]
expect(callArgs.body.variant).toBe("max")
expect(callArgs.body.agent).toBe("sisyphus-junior")
expect(callArgs.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
}, { timeout: 10000 })
test("session_id with background=true should return immediately without waiting", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockTask = {
id: "task-456",
sessionID: "ses_bg_continue",
description: "Background continued task",
agent: "explore",
status: "running",
}
const mockManager = {
resume: async () => mockTask,
}
const mockClient = {
session: {
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Continue bg test",
prompt: "Continue in background",
session_id: "ses_bg_continue",
run_in_background: true,
load_skills: ["git-master"],
},
toolContext
)
// then - should return background message
expect(result).toContain("Background task continued")
expect(result).toContain("task-456")
})
})
describe("sync mode new task (run_in_background=false)", () => {
test("sync mode prompt error returns error message immediately", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({}),
}
const promptMock = async () => {
throw new Error("JSON Parse error: Unexpected EOF")
}
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_sync_error_test" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [{ name: "ultrabrain", mode: "subagent" }] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Sync error test",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should return detailed error message with args and stack trace
expect(result).toContain("Send prompt failed")
expect(result).toContain("JSON Parse error")
expect(result).toContain("**Arguments**:")
expect(result).toContain("**Stack Trace**:")
})
test("sync mode success returns task result with content", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({}),
}
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_sync_success" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{
info: { id: "msg_001", role: "user", time: { created: Date.now() } },
parts: [{ type: "text", text: "Do something" }],
},
{
info: { id: "msg_002", role: "assistant", time: { created: Date.now() + 1 }, finish: "end_turn" },
parts: [{ type: "text", text: "Sync task completed successfully" }],
},
],
}),
status: async () => ({ data: { "ses_sync_success": { type: "idle" } } }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [{ name: "ultrabrain", mode: "subagent" }] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Sync success test",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should return the task result content
expect(result).toContain("Sync task completed successfully")
expect(result).toContain("Task completed")
}, { timeout: 20000 })
test("sync mode agent not found returns helpful error", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({}),
}
const promptMock = async () => {
throw new Error("Cannot read property 'name' of undefined agent.name")
}
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_agent_notfound" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [{ name: "ultrabrain", mode: "subagent" }] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Agent not found test",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should return agent not found error
expect(result).toContain("not found")
expect(result).toContain("registered")
})
test("sync mode passes category model to prompt", async () => {
// given
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_sync_model" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
}),
status: async () => ({ data: {} }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: { agents: async () => ({ data: [] }) },
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"custom-cat": { model: "provider/custom-model" }
}
})
const toolContext = {
sessionID: "parent",
messageID: "msg",
agent: "sisyphus",
abort: new AbortController().signal
}
// when
await tool.execute({
description: "Sync model test",
prompt: "test",
category: "custom-cat",
run_in_background: false,
load_skills: ["git-master"]
}, toolContext)
// then
expect(promptBody.model).toEqual({
providerID: "provider",
modelID: "custom-model"
})
}, { timeout: 20000 })
})
describe("unstable agent forced background mode", () => {
test("gemini model with run_in_background=false should force background but wait for result", async () => {
// given - category using gemini model with run_in_background=false
const { createDelegateTask } = require("./tools")
let launchCalled = false
const launchedTask = {
id: "task-unstable",
sessionID: "ses_unstable_gemini",
description: "Unstable gemini task",
agent: "sisyphus-junior",
status: "running",
}
const mockManager = {
launch: async () => {
launchCalled = true
return launchedTask
},
getTask: () => launchedTask,
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ provider: "google", id: "gemini-3-pro" }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_unstable_gemini" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{ info: { role: "assistant", time: { created: Date.now() } }, parts: [{ type: "text", text: "Gemini task completed successfully" }] }
]
}),
status: async () => ({ data: { "ses_unstable_gemini": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using visual-engineering (gemini model) with run_in_background=false
const result = await tool.execute(
{
description: "Test gemini forced background",
prompt: "Do something visual",
category: "visual-engineering",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect(launchCalled).toBe(true)
expect(result).toContain("SUPERVISED TASK COMPLETED")
expect(result).toContain("Gemini task completed successfully")
}, { timeout: 20000 })
test("gemini model with run_in_background=true should not show unstable message (normal background)", async () => {
// given - category using gemini model with run_in_background=true (normal background flow)
const { createDelegateTask } = require("./tools")
let launchCalled = false
const mockManager = {
launch: async () => {
launchCalled = true
return {
id: "task-normal-bg",
sessionID: "ses_normal_bg",
description: "Normal background task",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using visual-engineering with run_in_background=true (normal background)
const result = await tool.execute(
{
description: "Test normal background",
prompt: "Do something visual",
category: "visual-engineering",
run_in_background: true, // User explicitly says true - normal background
load_skills: ["git-master"],
},
toolContext
)
// then - should NOT show unstable message (it's normal background flow)
expect(launchCalled).toBe(true)
expect(result).not.toContain("UNSTABLE AGENT MODE")
expect(result).toContain("task-normal-bg")
})
test("minimax model with run_in_background=false should force background but wait for result", async () => {
// given - custom category using minimax model with run_in_background=false
const { createDelegateTask } = require("./tools")
let launchCalled = false
const launchedTask = {
id: "task-unstable-minimax",
sessionID: "ses_unstable_minimax",
description: "Unstable minimax task",
agent: "sisyphus-junior",
status: "running",
}
const mockManager = {
launch: async () => {
launchCalled = true
return launchedTask
},
getTask: () => launchedTask,
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_unstable_minimax" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{ info: { role: "assistant", time: { created: Date.now() } }, parts: [{ type: "text", text: "Minimax task completed successfully" }] }
]
}),
status: async () => ({ data: { "ses_unstable_minimax": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"minimax-cat": {
model: "minimax/abab-5",
},
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using minimax category with run_in_background=false
const result = await tool.execute(
{
description: "Test minimax forced background",
prompt: "Do something with minimax",
category: "minimax-cat",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect(launchCalled).toBe(true)
expect(result).toContain("SUPERVISED TASK COMPLETED")
expect(result).toContain("Minimax task completed successfully")
}, { timeout: 20000 })
test("non-gemini model with run_in_background=false should run sync (not forced to background)", async () => {
// given - category using non-gemini model with run_in_background=false
const { createDelegateTask } = require("./tools")
let launchCalled = false
let promptCalled = false
const mockManager = {
launch: async () => {
launchCalled = true
return { id: "should-not-be-called", sessionID: "x", description: "x", agent: "x", status: "running" }
},
}
const promptMock = async () => {
promptCalled = true
return { data: {} }
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_sync_non_gemini" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done sync" }] }]
}),
status: async () => ({ data: { "ses_sync_non_gemini": { type: "idle" } } }),
},
}
// Use ultrabrain which uses gpt-5.2 (non-gemini)
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using ultrabrain (gpt model) with run_in_background=false
const result = await tool.execute(
{
description: "Test non-gemini sync",
prompt: "Do something smart",
category: "ultrabrain",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should run sync, NOT forced to background
expect(launchCalled).toBe(false) // manager.launch should NOT be called
expect(promptCalled).toBe(true) // sync mode uses session.prompt
expect(result).not.toContain("UNSTABLE AGENT MODE")
}, { timeout: 20000 })
test("artistry category (gemini) with run_in_background=false should force background but wait for result", async () => {
// given - artistry also uses gemini model
const { createDelegateTask } = require("./tools")
let launchCalled = false
const launchedTask = {
id: "task-artistry",
sessionID: "ses_artistry_gemini",
description: "Artistry gemini task",
agent: "sisyphus-junior",
status: "running",
}
const mockManager = {
launch: async () => {
launchCalled = true
return launchedTask
},
getTask: () => launchedTask,
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ provider: "google", id: "gemini-3-pro" }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_artistry_gemini" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{ info: { role: "assistant", time: { created: Date.now() } }, parts: [{ type: "text", text: "Artistry result here" }] }
]
}),
status: async () => ({ data: { "ses_artistry_gemini": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - artistry category (gemini-3-pro with high variant)
const result = await tool.execute(
{
description: "Test artistry forced background",
prompt: "Do something artistic",
category: "artistry",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect(launchCalled).toBe(true)
expect(result).toContain("SUPERVISED TASK COMPLETED")
expect(result).toContain("Artistry result here")
}, { timeout: 20000 })
test("writing category (gemini-flash) with run_in_background=false should force background but wait for result", async () => {
// given - writing uses gemini-3-flash
const { createDelegateTask } = require("./tools")
let launchCalled = false
const launchedTask = {
id: "task-writing",
sessionID: "ses_writing_gemini",
description: "Writing gemini task",
agent: "sisyphus-junior",
status: "running",
}
const mockManager = {
launch: async () => {
launchCalled = true
return launchedTask
},
getTask: () => launchedTask,
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ provider: "google", id: "gemini-3-flash" }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_writing_gemini" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{ info: { role: "assistant", time: { created: Date.now() } }, parts: [{ type: "text", text: "Writing result here" }] }
]
}),
status: async () => ({ data: { "ses_writing_gemini": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - writing category (gemini-3-flash)
const result = await tool.execute(
{
description: "Test writing forced background",
prompt: "Write something",
category: "writing",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect(launchCalled).toBe(true)
expect(result).toContain("SUPERVISED TASK COMPLETED")
expect(result).toContain("Writing result here")
}, { timeout: 20000 })
test("is_unstable_agent=true should force background but wait for result", async () => {
// given - custom category with is_unstable_agent=true but non-gemini model
const { createDelegateTask } = require("./tools")
let launchCalled = false
const launchedTask = {
id: "task-custom-unstable",
sessionID: "ses_custom_unstable",
description: "Custom unstable task",
agent: "sisyphus-junior",
status: "running",
}
const mockManager = {
launch: async () => {
launchCalled = true
return launchedTask
},
getTask: () => launchedTask,
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_custom_unstable" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [
{ info: { role: "assistant", time: { created: Date.now() } }, parts: [{ type: "text", text: "Custom unstable result" }] }
]
}),
status: async () => ({ data: { "ses_custom_unstable": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"my-unstable-cat": {
model: "openai/gpt-5.2",
is_unstable_agent: true,
},
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using custom unstable category with run_in_background=false
const result = await tool.execute(
{
description: "Test custom unstable",
prompt: "Do something",
category: "my-unstable-cat",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect(launchCalled).toBe(true)
expect(result).toContain("SUPERVISED TASK COMPLETED")
expect(result).toContain("Custom unstable result")
}, { timeout: 20000 })
})
describe("category model resolution fallback", () => {
test("category uses resolved.model when connectedProvidersCache is null and availableModels is empty", async () => {
// given - connectedProvidersCache returns null (simulates missing cache file)
// This is a regression test for PR #1227 which removed resolved.model from userModel chain
cacheSpy.mockReturnValue(null)
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-fallback",
sessionID: "ses_fallback_test",
description: "Fallback test task",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
// NO userCategories override, NO sisyphusJuniorModel
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
// userCategories: undefined - use DEFAULT_CATEGORIES only
// sisyphusJuniorModel: undefined
connectedProvidersOverride: null,
availableModelsOverride: new Set(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using "quick" category which should use "anthropic/claude-haiku-4-5"
await tool.execute(
{
description: "Test category fallback",
prompt: "Do something quick",
category: "quick",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - model should be anthropic/claude-haiku-4-5 from DEFAULT_CATEGORIES
// NOT anthropic/claude-sonnet-4-5 (system default)
expect(launchInput.model.providerID).toBe("anthropic")
expect(launchInput.model.modelID).toBe("claude-haiku-4-5")
})
test("category delegation ignores UI-selected (Kimi) system default model", async () => {
// given - OpenCode system default model is Kimi (selected from UI)
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-ui-model",
sessionID: "ses_ui_model_test",
description: "UI model inheritance test",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"fallback-test": { model: "anthropic/claude-opus-4-6" },
},
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using "quick" category which should use "anthropic/claude-haiku-4-5"
await tool.execute(
{
description: "UI model inheritance test",
prompt: "Do something quick",
category: "quick",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - category model must win (not Kimi)
expect(launchInput.model.providerID).toBe("anthropic")
expect(launchInput.model.modelID).toBe("claude-haiku-4-5")
})
test("sisyphus-junior model override takes precedence over category model", async () => {
// given - sisyphus-junior override model differs from category default
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-override",
sessionID: "ses_override_test",
description: "Override precedence test",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using ultrabrain category (default model is openai/gpt-5.3-codex)
await tool.execute(
{
description: "Override precedence test",
prompt: "Do something",
category: "ultrabrain",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - override model should be used instead of category model
expect(launchInput.model.providerID).toBe("anthropic")
expect(launchInput.model.modelID).toBe("claude-sonnet-4-5")
})
test("explicit category model takes precedence over sisyphus-junior model", async () => {
// given - explicit category model differs from sisyphus-junior override
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-category-precedence",
sessionID: "ses_category_precedence_test",
description: "Category precedence test",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
userCategories: {
ultrabrain: { model: "openai/gpt-5.3-codex" },
},
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using ultrabrain category with explicit model override
await tool.execute(
{
description: "Category precedence test",
prompt: "Do something",
category: "ultrabrain",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - explicit category model should win
expect(launchInput.model.providerID).toBe("openai")
expect(launchInput.model.modelID).toBe("gpt-5.3-codex")
})
test("sisyphus-junior model override works with quick category (#1295)", async () => {
// given - user configures agents.sisyphus-junior.model but uses quick category
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-1295-quick",
sessionID: "ses_1295_quick",
description: "Issue 1295 regression",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using quick category (default: anthropic/claude-haiku-4-5)
await tool.execute(
{
description: "Issue 1295 quick category test",
prompt: "Quick task",
category: "quick",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - sisyphus-junior override model should be used, not category default
expect(launchInput.model.providerID).toBe("anthropic")
expect(launchInput.model.modelID).toBe("claude-sonnet-4-5")
})
test("sisyphus-junior model override works with user-defined category (#1295)", async () => {
// given - user has a custom category with no model requirement
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-1295-custom",
sessionID: "ses_1295_custom",
description: "Issue 1295 custom category",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
sisyphusJuniorModel: "openai/gpt-5.2",
userCategories: {
"my-custom": { temperature: 0.5 },
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - using custom category with no explicit model
await tool.execute(
{
description: "Custom category with agent model",
prompt: "Do something custom",
category: "my-custom",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - sisyphus-junior override model should be used as fallback
expect(launchInput.model.providerID).toBe("openai")
expect(launchInput.model.modelID).toBe("gpt-5.2")
})
})
describe("browserProvider propagation", () => {
test("should resolve agent-browser skill when browserProvider is passed", async () => {
// given - task configured with browserProvider: "agent-browser"
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_browser_provider" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
}),
status: async () => ({ data: {} }),
},
}
// Pass browserProvider to createDelegateTask
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
browserProvider: "agent-browser",
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - request agent-browser skill
await tool.execute(
{
description: "Test browserProvider propagation",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: ["agent-browser"],
},
toolContext
)
// then - agent-browser skill should be resolved (not in notFound)
expect(promptBody).toBeDefined()
expect(promptBody.system).toBeDefined()
expect(promptBody.system).toContain("agent-browser")
}, { timeout: 20000 })
test("should NOT resolve agent-browser skill when browserProvider is not set", async () => {
// given - task without browserProvider (defaults to playwright)
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_no_browser_provider" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
}),
status: async () => ({ data: {} }),
},
}
// No browserProvider passed
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - request agent-browser skill without browserProvider
const result = await tool.execute(
{
description: "Test missing browserProvider",
prompt: "Do something",
category: "ultrabrain",
run_in_background: false,
load_skills: ["agent-browser"],
},
toolContext
)
// then - should return skill not found error
expect(result).toContain("Skills not found")
expect(result).toContain("agent-browser")
})
})
describe("buildSystemContent", () => {
test("returns undefined when no skills and no category promptAppend", () => {
// given
const { buildSystemContent } = require("./tools")
// when
const result = buildSystemContent({ skillContent: undefined, categoryPromptAppend: undefined })
// then
expect(result).toBeUndefined()
})
test("returns skill content only when skills provided without category", () => {
// given
const { buildSystemContent } = require("./tools")
const skillContent = "You are a playwright expert"
// when
const result = buildSystemContent({ skillContent, categoryPromptAppend: undefined })
// then
expect(result).toBe(skillContent)
})
test("returns category promptAppend only when no skills", () => {
// given
const { buildSystemContent } = require("./tools")
const categoryPromptAppend = "Focus on visual design"
// when
const result = buildSystemContent({ skillContent: undefined, categoryPromptAppend })
// then
expect(result).toBe(categoryPromptAppend)
})
test("combines skill content and category promptAppend with separator", () => {
// given
const { buildSystemContent } = require("./tools")
const skillContent = "You are a playwright expert"
const categoryPromptAppend = "Focus on visual design"
// when
const result = buildSystemContent({ skillContent, categoryPromptAppend })
// then
expect(result).toContain(skillContent)
expect(result).toContain(categoryPromptAppend)
expect(result).toContain("\n\n")
})
test("prepends plan agent system prompt when agentName is 'plan'", () => {
// given
const { buildSystemContent } = require("./tools")
const { buildPlanAgentSystemPrepend } = require("./constants")
const availableCategories = [
{
name: "deep",
description: "Goal-oriented autonomous problem-solving",
model: "openai/gpt-5.3-codex",
},
]
const availableSkills = [
{
name: "typescript-programmer",
description: "Production TypeScript code.",
location: "plugin",
},
]
// when
const result = buildSystemContent({
agentName: "plan",
availableCategories,
availableSkills,
})
// then
expect(result).toContain("<system>")
expect(result).toContain("MANDATORY CONTEXT GATHERING PROTOCOL")
expect(result).toContain("### AVAILABLE CATEGORIES")
expect(result).toContain("`deep`")
expect(result).not.toContain("prompt-engineer")
expect(result).toBe(buildPlanAgentSystemPrepend(availableCategories, availableSkills))
})
test("does not prepend plan agent prompt for prometheus agent", () => {
//#given - prometheus is NOT a plan agent (decoupled)
const { buildSystemContent } = require("./tools")
const skillContent = "You are a strategic planner"
//#when
const result = buildSystemContent({
skillContent,
agentName: "prometheus",
})
//#then - prometheus should NOT get plan agent system prepend
expect(result).toBe(skillContent)
expect(result).not.toContain("MANDATORY CONTEXT GATHERING PROTOCOL")
})
test("does not prepend plan agent prompt for Prometheus (case insensitive)", () => {
//#given - Prometheus (capitalized) is NOT a plan agent
const { buildSystemContent } = require("./tools")
const skillContent = "You are a strategic planner"
//#when
const result = buildSystemContent({
skillContent,
agentName: "Prometheus",
})
//#then
expect(result).toBe(skillContent)
expect(result).not.toContain("MANDATORY CONTEXT GATHERING PROTOCOL")
})
test("combines plan agent prepend with skill content", () => {
// given
const { buildSystemContent } = require("./tools")
const { buildPlanAgentSystemPrepend } = require("./constants")
const skillContent = "You are a planning expert"
const availableCategories = [
{
name: "writing",
description: "Documentation, prose, technical writing",
model: "kimi-for-coding/k2p5",
},
]
const availableSkills = [
{
name: "python-programmer",
description: "Production Python code.",
location: "plugin",
},
]
const planPrepend = buildPlanAgentSystemPrepend(availableCategories, availableSkills)
// when
const result = buildSystemContent({
skillContent,
agentName: "plan",
availableCategories,
availableSkills,
})
// then
expect(result).toContain(planPrepend)
expect(result).toContain(skillContent)
expect(result!.indexOf(planPrepend)).toBeLessThan(result!.indexOf(skillContent))
})
test("does not prepend plan agent prompt for non-plan agents", () => {
// given
const { buildSystemContent } = require("./tools")
const skillContent = "You are an expert"
// when
const result = buildSystemContent({ skillContent, agentName: "oracle" })
// then
expect(result).toBe(skillContent)
expect(result).not.toContain("<system>")
})
test("does not prepend plan agent prompt when agentName is undefined", () => {
// given
const { buildSystemContent } = require("./tools")
const skillContent = "You are an expert"
// when
const result = buildSystemContent({ skillContent, agentName: undefined })
// then
expect(result).toBe(skillContent)
expect(result).not.toContain("<system>")
})
})
describe("modelInfo detection via resolveCategoryConfig", () => {
test("catalog model is used for category with catalog entry", () => {
// given - ultrabrain has catalog entry
const categoryName = "ultrabrain"
// when
const resolved = resolveCategoryConfig(categoryName, { systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - catalog model is used
expect(resolved).not.toBeNull()
expect(resolved!.config.model).toBe("openai/gpt-5.3-codex")
expect(resolved!.config.variant).toBe("xhigh")
})
test("default model is used for category with default entry", () => {
// given - unspecified-low has default model
const categoryName = "unspecified-low"
// when
const resolved = resolveCategoryConfig(categoryName, { systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - default model from DEFAULT_CATEGORIES is used
expect(resolved).not.toBeNull()
expect(resolved!.config.model).toBe("anthropic/claude-sonnet-4-5")
})
test("category built-in model takes precedence over inheritedModel for builtin category", () => {
// given - builtin ultrabrain category with its own model, inherited model also provided
const categoryName = "ultrabrain"
const inheritedModel = "cliproxy/claude-opus-4-6"
// when
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - category's built-in model wins (ultrabrain uses gpt-5.3-codex)
expect(resolved).not.toBeNull()
const actualModel = resolved!.config.model
expect(actualModel).toBe("openai/gpt-5.3-codex")
})
test("when user defines model - modelInfo should report user-defined regardless of inheritedModel", () => {
// given
const categoryName = "ultrabrain"
const userCategories = { "ultrabrain": { model: "my-provider/custom-model" } }
const inheritedModel = "cliproxy/claude-opus-4-6"
// when
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then - actualModel should be userModel, type should be "user-defined"
expect(resolved).not.toBeNull()
const actualModel = resolved!.config.model
const userDefinedModel = userCategories[categoryName]?.model
expect(actualModel).toBe(userDefinedModel)
expect(actualModel).toBe("my-provider/custom-model")
})
test("detection logic: actualModel comparison correctly identifies source", () => {
// given - This test verifies the fix for PR #770 bug
// The bug was: checking `if (inheritedModel)` instead of `if (actualModel === inheritedModel)`
const categoryName = "ultrabrain"
const inheritedModel = "cliproxy/claude-opus-4-6"
const userCategories = { "ultrabrain": { model: "user/model" } }
// when - user model wins
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
const actualModel = resolved!.config.model
const userDefinedModel = userCategories[categoryName]?.model
// then - detection should compare against actual resolved model
const detectedType = actualModel === userDefinedModel
? "user-defined"
: actualModel === inheritedModel
? "inherited"
: actualModel === SYSTEM_DEFAULT_MODEL
? "system-default"
: undefined
expect(detectedType).toBe("user-defined")
expect(actualModel).not.toBe(inheritedModel)
})
// ===== TESTS FOR resolveModel() INTEGRATION (TDD GREEN) =====
// These tests verify the NEW behavior where categories do NOT have default models
test("FIXED: category built-in model takes precedence over inheritedModel", () => {
// given a builtin category with its own model, and an inherited model from parent
// The CORRECT chain: userConfig?.model ?? categoryBuiltIn ?? systemDefaultModel
const categoryName = "ultrabrain"
const inheritedModel = "anthropic/claude-opus-4-6"
// when category has a built-in model (gpt-5.3-codex for ultrabrain)
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then category's built-in model should be used, NOT inheritedModel
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe("openai/gpt-5.3-codex")
})
test("FIXED: systemDefaultModel is used when no userConfig.model and no inheritedModel", () => {
// given a custom category with no default model
const categoryName = "custom-no-default"
const userCategories = { "custom-no-default": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
const systemDefaultModel = "anthropic/claude-sonnet-4-5"
// when no inheritedModel is provided, only systemDefaultModel
const resolved = resolveCategoryConfig(categoryName, {
userCategories,
systemDefaultModel
})
// then systemDefaultModel should be returned
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe("anthropic/claude-sonnet-4-5")
})
test("FIXED: userConfig.model always takes priority over everything", () => {
// given userConfig.model is explicitly set
const categoryName = "ultrabrain"
const userCategories = { "ultrabrain": { model: "custom/user-model" } }
const inheritedModel = "anthropic/claude-opus-4-6"
const systemDefaultModel = "anthropic/claude-sonnet-4-5"
// when resolveCategoryConfig is called with all sources
const resolved = resolveCategoryConfig(categoryName, {
userCategories,
inheritedModel,
systemDefaultModel
})
// then userConfig.model should win
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe("custom/user-model")
})
test("FIXED: empty string in userConfig.model is treated as unset and falls back to systemDefault", () => {
// given userConfig.model is empty string "" for a custom category (no built-in model)
const categoryName = "custom-empty-model"
const userCategories = { "custom-empty-model": { model: "", temperature: 0.3 } }
const inheritedModel = "anthropic/claude-opus-4-6"
// when resolveCategoryConfig is called
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then should fall back to systemDefaultModel since custom category has no built-in model
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe(SYSTEM_DEFAULT_MODEL)
})
test("FIXED: undefined userConfig.model falls back to category built-in model", () => {
// given user sets a builtin category but leaves model undefined
const categoryName = "visual-engineering"
// Using type assertion since we're testing fallback behavior for categories without model
const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record<string, CategoryConfig>
const inheritedModel = "anthropic/claude-opus-4-6"
// when resolveCategoryConfig is called
const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
// then should use category's built-in model (gemini-3-pro for visual-engineering)
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe("google/gemini-3-pro")
})
test("systemDefaultModel is used when no other model is available", () => {
// given - custom category with no model, but systemDefaultModel is set
const categoryName = "my-custom"
// Using type assertion since we're testing fallback behavior for categories without model
const userCategories = { "my-custom": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
const systemDefaultModel = "anthropic/claude-sonnet-4-5"
// when
const resolved = resolveCategoryConfig(categoryName, { userCategories, systemDefaultModel })
// then - actualModel should be systemDefaultModel
expect(resolved).not.toBeNull()
expect(resolved!.model).toBe(systemDefaultModel)
})
})
describe("plan family mutual delegation block", () => {
test("plan cannot delegate to plan (self-delegation)", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockClient = {
app: { agents: async () => ({ data: [{ name: "plan", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "s" } }), prompt: async () => ({ data: {} }), promptAsync: async () => ({ data: {} }), messages: async () => ({ data: [] }), status: async () => ({ data: {} }) },
}
const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient })
//#when
const result = await tool.execute(
{ description: "test", prompt: "Create a plan", subagent_type: "plan", run_in_background: false, load_skills: [] },
{ sessionID: "p", messageID: "m", agent: "plan", abort: new AbortController().signal }
)
//#then
expect(result).toContain("plan-family")
expect(result).toContain("directly")
})
test("prometheus cannot delegate to plan (cross-blocking)", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockClient = {
app: { agents: async () => ({ data: [{ name: "plan", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "s" } }), prompt: async () => ({ data: {} }), promptAsync: async () => ({ data: {} }), messages: async () => ({ data: [] }), status: async () => ({ data: {} }) },
}
const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient })
//#when
const result = await tool.execute(
{ description: "test", prompt: "Create a plan", subagent_type: "plan", run_in_background: false, load_skills: [] },
{ sessionID: "p", messageID: "m", agent: "prometheus", abort: new AbortController().signal }
)
//#then
expect(result).toContain("plan-family")
})
test("plan cannot delegate to prometheus (cross-blocking)", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "s" } }), prompt: async () => ({ data: {} }), promptAsync: async () => ({ data: {} }), messages: async () => ({ data: [] }), status: async () => ({ data: {} }) },
}
const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient })
//#when
const result = await tool.execute(
{ description: "test", prompt: "Execute", subagent_type: "prometheus", run_in_background: false, load_skills: [] },
{ sessionID: "p", messageID: "m", agent: "plan", abort: new AbortController().signal }
)
//#then
expect(result).toContain("plan-family")
})
test("sisyphus CAN delegate to plan (not in plan family)", async () => {
//#given
const { createDelegateTask } = require("./tools")
const mockClient = {
app: { agents: async () => ({ data: [{ name: "plan", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_ok" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }] }),
status: async () => ({ data: { "ses_ok": { type: "idle" } } }),
},
}
const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient })
//#when
const result = await tool.execute(
{ description: "test", prompt: "Create a plan", subagent_type: "plan", run_in_background: false, load_skills: [] },
{ sessionID: "p", messageID: "m", agent: "sisyphus", abort: new AbortController().signal }
)
//#then
expect(result).not.toContain("plan-family")
expect(result).toContain("Plan created")
}, { timeout: 20000 })
})
describe("subagent_type model extraction (issue #1225)", () => {
test("background mode passes matched agent model to manager.launch", async () => {
// given - agent with model registered, using subagent_type with run_in_background=true
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-explore",
sessionID: "ses_explore_model",
description: "Explore task",
agent: "explore",
status: "running",
}
},
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "explore", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-haiku-4-5" } },
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
create: async () => ({ data: { id: "ses_explore_model" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to explore agent via subagent_type
await tool.execute(
{
description: "Explore codebase",
prompt: "Find auth patterns",
subagent_type: "explore",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - matched agent's model should be passed to manager.launch
expect(launchInput.model).toEqual({
providerID: "anthropic",
modelID: "claude-haiku-4-5",
})
})
test("sync mode passes matched agent model to session.prompt", async () => {
// given - agent with model registered, using subagent_type with run_in_background=false
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } },
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_oracle_model" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Consultation done" }] }],
}),
status: async () => ({ data: { "ses_oracle_model": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to oracle agent via subagent_type in sync mode
await tool.execute(
{
description: "Consult oracle",
prompt: "Review architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - matched agent's model should be passed to session.prompt
expect(promptBody.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
})
}, { timeout: 20000 })
test("agent without model resolves via fallback chain", async () => {
// given - agent registered without model field, fallback chain should resolve
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "explore", mode: "subagent" },
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_no_model_agent" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
}),
status: async () => ({ data: { "ses_no_model_agent": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to agent without model
await tool.execute(
{
description: "Explore without model",
prompt: "Find something",
subagent_type: "explore",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - model should be resolved via AGENT_MODEL_REQUIREMENTS fallback chain
expect(promptBody.model).toBeDefined()
}, { timeout: 20000 })
test("agentOverrides model takes priority over matchedAgent.model (#1357)", async () => {
// given - user configured oracle to use a specific model in oh-my-opencode.json
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_override_model" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
}),
status: async () => ({ data: { "ses_override_model": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
agentOverrides: {
oracle: { model: "anthropic/claude-opus-4-6" },
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to oracle via subagent_type with user override
await tool.execute(
{
description: "Consult oracle with override",
prompt: "Review architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - user-configured model should take priority over matchedAgent.model
expect(promptBody.model).toEqual({
providerID: "anthropic",
modelID: "claude-opus-4-6",
})
}, { timeout: 20000 })
test("agentOverrides variant is applied when model is overridden (#1357)", async () => {
// given - user configured oracle with model and variant
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_variant_test" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
}),
status: async () => ({ data: { "ses_variant_test": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
agentOverrides: {
oracle: { model: "anthropic/claude-opus-4-6", variant: "max" },
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to oracle via subagent_type with variant override
await tool.execute(
{
description: "Consult oracle with variant",
prompt: "Review architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - user-configured variant should be applied
expect(promptBody.variant).toBe("max")
}, { timeout: 20000 })
test("fallback chain resolves model when no override and no matchedAgent.model (#1357)", async () => {
// given - agent registered without model, no override, but AGENT_MODEL_REQUIREMENTS has fallback
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: {
agents: async () => ({
data: [
{ name: "oracle", mode: "subagent" }, // no model field
],
}),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_fallback_test" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
}),
status: async () => ({ data: { "ses_fallback_test": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
// no agentOverrides
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
availableModelsOverride: createTestAvailableModels(),
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - delegating to oracle with no override and no matchedAgent model
await tool.execute(
{
description: "Consult oracle with fallback",
prompt: "Review architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
// oracle fallback chain: gpt-5.2 (openai) > gemini-3-pro (google) > claude-opus-4-6 (anthropic)
// Since openai is in connectedProviders, should resolve to openai/gpt-5.2
expect(promptBody.model).toBeDefined()
expect(promptBody.model.providerID).toBe("openai")
expect(promptBody.model.modelID).toContain("gpt-5.2")
}, { timeout: 20000 })
})
describe("subagent task permission", () => {
test("plan subagent should have task permission enabled", async () => {
//#given - sisyphus delegates to plan agent
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const promptMock = async (input: any) => {
promptBody = input.body
return { data: {} }
}
const mockClient = {
app: { agents: async () => ({ data: [{ name: "plan", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_plan_delegate" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }]
}),
status: async () => ({ data: { "ses_plan_delegate": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
//#when - sisyphus delegates to plan
await tool.execute(
{
description: "Test plan task permission",
prompt: "Create a plan",
subagent_type: "plan",
run_in_background: false,
load_skills: [],
},
toolContext
)
//#then - plan agent should have task permission
expect(promptBody.tools.task).toBe(true)
}, { timeout: 20000 })
test("prometheus subagent should have task permission (plan family)", async () => {
//#given
const { createDelegateTask } = require("./tools")
let promptBody: any
const promptMock = async (input: any) => { promptBody = input.body; return { data: {} } }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_prometheus_task" } }),
prompt: promptMock,
promptAsync: promptMock,
messages: async () => ({ data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }] }),
status: async () => ({ data: { "ses_prometheus_task": { type: "idle" } } }),
},
}
const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient })
//#when
await tool.execute(
{ description: "Test prometheus task permission", prompt: "Create a plan", subagent_type: "prometheus", run_in_background: false, load_skills: [] },
{ sessionID: "p", messageID: "m", agent: "sisyphus", abort: new AbortController().signal }
)
//#then
expect(promptBody.tools.task).toBe(true)
}, { timeout: 20000 })
test("non-plan subagent should NOT have task permission", async () => {
//#given - sisyphus delegates to oracle (non-plan)
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_oracle_no_delegate" } }),
prompt: async (input: any) => {
promptBody = input.body
return { data: {} }
},
promptAsync: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Consultation done" }] }]
}),
status: async () => ({ data: { "ses_oracle_no_delegate": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - sisyphus delegates to oracle
await tool.execute(
{
description: "Test oracle no task permission",
prompt: "Consult on architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - oracle should NOT have task permission
expect(promptBody.tools.task).toBe(false)
}, { timeout: 20000 })
})
describe("session title and metadata format (OpenCode compatibility)", () => {
test("sync session title follows OpenCode format: '{description} (@{agent} subagent)'", async () => {
// given
const { createDelegateTask } = require("./tools")
let createBody: any
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ id: SYSTEM_DEFAULT_MODEL }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async (input: any) => {
createBody = input.body
return { data: { id: "ses_title_test" } }
},
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "done" }] }]
}),
status: async () => ({ data: { "ses_title_test": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when - sync task with category
await tool.execute(
{
description: "Implement feature X",
prompt: "Build the feature",
category: "quick",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - title should follow OpenCode format
expect(createBody.title).toBe("Implement feature X (@sisyphus-junior subagent)")
}, { timeout: 10000 })
test("sync task output includes <task_metadata> block with session_id", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [{ id: SYSTEM_DEFAULT_MODEL }] },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_metadata_test" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Task completed" }] }]
}),
status: async () => ({ data: { "ses_metadata_test": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Test metadata format",
prompt: "Do something",
category: "quick",
run_in_background: false,
load_skills: [],
},
toolContext
)
// then - output should contain <task_metadata> block
expect(result).toContain("<task_metadata>")
expect(result).toContain("session_id: ses_metadata_test")
expect(result).toContain("</task_metadata>")
}, { timeout: 10000 })
test("background task output includes <task_metadata> block with session_id", async () => {
// given
const { createDelegateTask } = require("./tools")
const mockManager = {
launch: async () => ({
id: "bg_meta_test",
sessionID: "ses_bg_metadata",
description: "Background metadata test",
agent: "sisyphus-junior",
status: "running",
}),
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
userCategories: {
"sisyphus-junior": { model: "anthropic/claude-sonnet-4-5" },
},
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// when
const result = await tool.execute(
{
description: "Background metadata test",
prompt: "Do something",
category: "quick",
run_in_background: true,
load_skills: [],
},
toolContext
)
// then - output should contain <task_metadata> block
expect(result).toContain("<task_metadata>")
expect(result).toContain("session_id: ses_bg_metadata")
expect(result).toContain("</task_metadata>")
}, { timeout: 10000 })
})
})