feat(agents): add Hephaestus - autonomous deep worker agent (#1287)

* refactor(keyword-detector): split constants into domain-specific modules

* feat(shared): add requiresAnyModel and isAnyFallbackModelAvailable

* feat(config): add hephaestus to agent schemas

* feat(agents): add Hephaestus autonomous deep worker

* feat(cli): update model-fallback for hephaestus support

* feat(plugin): add hephaestus to config handler with ordering

* test(delegate-task): update tests for hephaestus agent

* docs: update AGENTS.md files for hephaestus

* docs: add hephaestus to READMEs

* chore: regenerate config schema

* fix(delegate-task): bypass requiresModel check when user provides explicit config

* docs(hephaestus): add 4-part context structure for explore/librarian prompts

* docs: fix review comments from cubic (non-breaking changes)

- Move Hephaestus from Primary Agents to Subagents (uses own fallback chain)
- Fix Hephaestus fallback chain documentation (claude-opus-4-5 → gemini-3-pro)
- Add settings.local.json to claude-code-hooks config sources
- Fix delegate_task parameters in ultrawork prompt (agent→subagent_type, background→run_in_background, add load_skills)
- Update line counts in AGENTS.md (index.ts: 788, manager.ts: 1440)

* docs: fix additional documentation inconsistencies from oracle review

- Fix delegate_task parameters in Background Agents example (docs/features.md)
- Fix Hephaestus fallback chain in root AGENTS.md to match model-requirements.ts

* docs: clarify Hephaestus has no fallback (requires gpt-5.2-codex only)

Hephaestus uses requiresModel constraint - it only activates when gpt-5.2-codex
is available. The fallback chain in code is unreachable, so documentation
should not mention fallbacks.

* fix(hephaestus): remove unreachable fallback chain entries

Hephaestus has requiresModel: gpt-5.2-codex which means the agent only
activates when that specific model is available. The fallback entries
(claude-opus-4-5, gemini-3-pro) were unreachable and misleading.

---------

Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
YeonGyu-Kim
2026-02-01 19:26:57 +09:00
committed by GitHub
parent 5f053cd75b
commit 64825158a7
45 changed files with 2617 additions and 970 deletions
+287 -111
View File
@@ -1,61 +1,78 @@
import { describe, test, expect, beforeEach, spyOn, afterEach } from "bun:test"
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test"
import { createBuiltinAgents } from "./utils"
import type { AgentConfig } from "@opencode-ai/sdk"
import { clearSkillCache } from "../features/opencode-skill-loader/skill-content"
import * as connectedProvidersCache from "../shared/connected-providers-cache"
import * as modelAvailability from "../shared/model-availability"
import * as shared from "../shared"
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-5"
describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus with default model has thinking config", async () => {
// given - no overrides, using systemDefaultModel
test("Sisyphus with default model has thinking config when all models available", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([
"anthropic/claude-opus-4-5",
"kimi-for-coding/k2p5",
"opencode/kimi-k2.5-free",
"zai-coding-plan/glm-4.7",
"opencode/glm-4.7-free",
])
)
// when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// then
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
// #then
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
// given
// #given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2" },
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then
// #then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.reasoningEffort).toBe("medium")
expect(agents.sisyphus.thinking).toBeUndefined()
})
test("Sisyphus uses system default when no availableModels provided", async () => {
// given
test("Sisyphus is not created when no availableModels provided (requiresAnyModel)", async () => {
// #given
const systemDefaultModel = "anthropic/claude-opus-4-5"
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
// when
const agents = await createBuiltinAgents([], {}, undefined, systemDefaultModel)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, systemDefaultModel, undefined, undefined, [], {})
// then - falls back to system default when no availability match
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
// #then
expect(agents.sisyphus).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
test("Oracle uses connected provider fallback when availableModels is empty and cache exists", async () => {
// given - connected providers cache has "openai", which matches oracle's first fallback entry
// #given - connected providers cache has "openai", which matches oracle's first fallback entry
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// when
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
// then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
// #then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.thinking).toBeUndefined()
@@ -63,28 +80,28 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("Oracle created without model field when no cache exists (first run scenario)", async () => {
// given - no cache at all (first run)
// #given - no cache at all (first run)
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
// when
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
// then - oracle should be created with system default model (fallback to systemDefaultModel)
// #then - oracle should be created with system default model (fallback to systemDefaultModel)
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe(TEST_DEFAULT_MODEL)
cacheSpy.mockRestore?.()
})
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
// given
// #given
const overrides = {
oracle: { model: "openai/gpt-5.2" },
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then
// #then
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.textVerbosity).toBe("high")
@@ -92,15 +109,15 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("Oracle with Claude model override has thinking, no reasoningEffort", async () => {
// given
// #given
const overrides = {
oracle: { model: "anthropic/claude-sonnet-4" },
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then
// #then
expect(agents.oracle.model).toBe("anthropic/claude-sonnet-4")
expect(agents.oracle.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.oracle.reasoningEffort).toBeUndefined()
@@ -108,15 +125,15 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("non-model overrides are still applied after factory rebuild", async () => {
// given
// #given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then
// #then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.temperature).toBe(0.5)
})
@@ -124,42 +141,197 @@ describe("createBuiltinAgents with model overrides", () => {
describe("createBuiltinAgents without systemDefaultModel", () => {
test("agents created via connected cache fallback even without systemDefaultModel", async () => {
// given - connected cache has "openai", which matches oracle's fallback chain
// #given - connected cache has "openai", which matches oracle's fallback chain
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// when
// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
// then - connected cache enables model resolution despite no systemDefaultModel
// #then - connected cache enables model resolution despite no systemDefaultModel
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2")
cacheSpy.mockRestore?.()
})
test("agents NOT created when no cache and no systemDefaultModel (first run without defaults)", async () => {
// given
// #given
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
// when
// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
// then
// #then
expect(agents.oracle).toBeUndefined()
cacheSpy.mockRestore?.()
})
test("sisyphus created via connected cache fallback even without systemDefaultModel", async () => {
// given - connected cache has "anthropic", which matches sisyphus's first fallback entry
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
test("sisyphus created via connected cache fallback when all providers available", async () => {
// #given
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue([
"anthropic", "kimi-for-coding", "opencode", "zai-coding-plan"
])
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([
"anthropic/claude-opus-4-5",
"kimi-for-coding/k2p5",
"opencode/kimi-k2.5-free",
"zai-coding-plan/glm-4.7",
"opencode/glm-4.7-free",
])
)
// when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined, undefined, undefined, [], {})
// then - connected cache enables model resolution despite no systemDefaultModel
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
cacheSpy.mockRestore?.()
})
// #then
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
} finally {
cacheSpy.mockRestore()
fetchSpy.mockRestore()
}
})
})
describe("createBuiltinAgents with requiresModel gating", () => {
test("hephaestus is not created when gpt-5.2-codex is unavailable", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-5"])
)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.hephaestus).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
test("hephaestus is created when gpt-5.2-codex is available", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2-codex"])
)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.hephaestus).toBeDefined()
} finally {
fetchSpy.mockRestore()
}
})
test("hephaestus is not created when availableModels is empty", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.hephaestus).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
test("hephaestus is created when explicit config provided even if model unavailable", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-5"])
)
const overrides = {
hephaestus: { model: "anthropic/claude-opus-4-5" },
}
try {
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.hephaestus).toBeDefined()
} finally {
fetchSpy.mockRestore()
}
})
})
describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
test("sisyphus is created when at least one fallback model is available", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-5"])
)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.sisyphus).toBeDefined()
} finally {
fetchSpy.mockRestore()
}
})
test("sisyphus is not created when availableModels is empty", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.sisyphus).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
test("sisyphus is created when explicit config provided even if no models available", async () => {
// #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
sisyphus: { model: "anthropic/claude-opus-4-5" },
}
try {
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.sisyphus).toBeDefined()
} finally {
fetchSpy.mockRestore()
}
})
test("sisyphus is not created when no fallback model is available (unrelated model only)", async () => {
// #given - only openai/gpt-5.2 available, not in sisyphus fallback chain
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.2"])
)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then
expect(agents.sisyphus).toBeUndefined()
} finally {
fetchSpy.mockRestore()
}
})
})
describe("buildAgent with category and skills", () => {
@@ -170,8 +342,12 @@ describe("buildAgent with category and skills", () => {
clearSkillCache()
})
afterEach(() => {
clearSkillCache()
})
test("agent with category inherits category settings", () => {
// given - agent factory that sets category but no model
// #given - agent factory that sets category but no model
const source = {
"test-agent": () =>
({
@@ -180,15 +356,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then - category's built-in model is applied
// #then - category's built-in model is applied
expect(agent.model).toBe("google/gemini-3-pro")
})
test("agent with category and existing model keeps existing model", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -198,15 +374,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then - explicit model takes precedence over category
// #then - explicit model takes precedence over category
expect(agent.model).toBe("custom/model")
})
test("agent with category inherits variant", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -222,16 +398,16 @@ describe("buildAgent with category and skills", () => {
},
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
// then
// #then
expect(agent.model).toBe("openai/gpt-5.2")
expect(agent.variant).toBe("xhigh")
})
test("agent with skills has content prepended to prompt", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -241,17 +417,17 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Original prompt content")
expect(agent.prompt).toMatch(/Designer-Turned-Developer[\s\S]*Original prompt content/s)
})
test("agent with multiple skills has all content prepended", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -261,16 +437,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Agent prompt")
})
test("agent without category or skills works as before", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -281,17 +457,17 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
expect(agent.model).toBe("custom/model")
expect(agent.temperature).toBe(0.5)
expect(agent.prompt).toBe("Base prompt")
})
test("agent with category and skills applies both", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -302,10 +478,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then - category's built-in model and skills are applied
// #then - category's built-in model and skills are applied
expect(agent.model).toBe("openai/gpt-5.2-codex")
expect(agent.variant).toBe("xhigh")
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
@@ -313,7 +489,7 @@ describe("buildAgent with category and skills", () => {
})
test("agent with non-existent category has no effect", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -323,10 +499,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
// Note: The factory receives model, but if category doesn't exist, it's not applied
// The agent's model comes from the factory output (which doesn't set model)
expect(agent.model).toBeUndefined()
@@ -334,7 +510,7 @@ describe("buildAgent with category and skills", () => {
})
test("agent with non-existent skills only prepends found ones", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -344,16 +520,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Base prompt")
})
test("agent with empty skills array keeps original prompt", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -363,15 +539,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when
// #when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then
// #then
expect(agent.prompt).toBe("Base prompt")
})
test("agent with agent-browser skill resolves when browserProvider is set", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -381,16 +557,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when - browserProvider is "agent-browser"
// #when - browserProvider is "agent-browser"
const agent = buildAgent(source["test-agent"], TEST_MODEL, undefined, undefined, "agent-browser")
// then - agent-browser skill content should be in prompt
// #then - agent-browser skill content should be in prompt
expect(agent.prompt).toContain("agent-browser")
expect(agent.prompt).toContain("Base prompt")
})
test("agent with agent-browser skill NOT resolved when browserProvider not set", () => {
// given
// #given
const source = {
"test-agent": () =>
({
@@ -400,10 +576,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// when - no browserProvider (defaults to playwright)
// #when - no browserProvider (defaults to playwright)
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// then - agent-browser skill not found, only base prompt remains
// #then - agent-browser skill not found, only base prompt remains
expect(agent.prompt).toBe("Base prompt")
expect(agent.prompt).not.toContain("agent-browser open")
})
@@ -411,36 +587,36 @@ describe("buildAgent with category and skills", () => {
describe("override.category expansion in createBuiltinAgents", () => {
test("standard agent override with category expands category properties", async () => {
// given
// #given
const overrides = {
oracle: { category: "ultrabrain" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2-codex")
expect(agents.oracle.variant).toBe("xhigh")
})
test("standard agent override with category AND direct variant - direct wins", async () => {
// given - ultrabrain has variant=xhigh, but direct override says "max"
// #given - ultrabrain has variant=xhigh, but direct override says "max"
const overrides = {
oracle: { category: "ultrabrain", variant: "max" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then - direct variant overrides category variant
// #then - direct variant overrides category variant
expect(agents.oracle).toBeDefined()
expect(agents.oracle.variant).toBe("max")
})
test("standard agent override with category AND direct reasoningEffort - direct wins", async () => {
// given - custom category has reasoningEffort=xhigh, direct override says "low"
// #given - custom category has reasoningEffort=xhigh, direct override says "low"
const categories = {
"test-cat": {
model: "openai/gpt-5.2",
@@ -451,16 +627,16 @@ describe("override.category expansion in createBuiltinAgents", () => {
oracle: { category: "test-cat", reasoningEffort: "low" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, categories)
// then - direct reasoningEffort wins over category
// #then - direct reasoningEffort wins over category
expect(agents.oracle).toBeDefined()
expect(agents.oracle.reasoningEffort).toBe("low")
})
test("standard agent override with category applies reasoningEffort from category when no direct override", async () => {
// given - custom category has reasoningEffort, no direct reasoningEffort in override
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
const categories = {
"reasoning-cat": {
model: "openai/gpt-5.2",
@@ -471,54 +647,54 @@ describe("override.category expansion in createBuiltinAgents", () => {
oracle: { category: "reasoning-cat" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, categories)
// then - category reasoningEffort is applied
// #then - category reasoningEffort is applied
expect(agents.oracle).toBeDefined()
expect(agents.oracle.reasoningEffort).toBe("high")
})
test("sisyphus override with category expands category properties", async () => {
// given
// #given
const overrides = {
sisyphus: { category: "ultrabrain" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(agents.sisyphus.variant).toBe("xhigh")
})
test("atlas override with category expands category properties", async () => {
// given
// #given
const overrides = {
atlas: { category: "ultrabrain" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.atlas).toBeDefined()
expect(agents.atlas.model).toBe("openai/gpt-5.2-codex")
expect(agents.atlas.variant).toBe("xhigh")
})
test("override with non-existent category has no effect on config", async () => {
// given
// #given
const overrides = {
oracle: { category: "non-existent-category" } as any,
}
// when
// #when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// then - no category-specific variant/reasoningEffort applied from non-existent category
// #then - no category-specific variant/reasoningEffort applied from non-existent category
expect(agents.oracle).toBeDefined()
const agentsWithoutOverride = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
expect(agents.oracle.model).toBe(agentsWithoutOverride.oracle.model)
@@ -527,7 +703,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
describe("Deadlock prevention - fetchAvailableModels must not receive client", () => {
test("createBuiltinAgents should call fetchAvailableModels with undefined client to prevent deadlock", async () => {
// given - This test ensures we don't regress on issue #1301
// #given - This test ensures we don't regress on issue #1301
// Passing client to fetchAvailableModels during createBuiltinAgents (called from config handler)
// causes deadlock:
// - Plugin init waits for server response (client.provider.list())
@@ -540,7 +716,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
model: { list: () => Promise.resolve({ data: [] }) },
}
// when - Even when client is provided, fetchAvailableModels must be called with undefined
// #when - Even when client is provided, fetchAvailableModels must be called with undefined
await createBuiltinAgents(
[],
{},
@@ -552,7 +728,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
mockClient // client is passed but should NOT be forwarded to fetchAvailableModels
)
// then - fetchAvailableModels must be called with undefined as first argument (no client)
// #then - fetchAvailableModels must be called with undefined as first argument (no client)
// This prevents the deadlock described in issue #1301
expect(fetchSpy).toHaveBeenCalled()
const firstCallArgs = fetchSpy.mock.calls[0]