feat(tools): wire team-mode into skill and subagent resolvers

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:48:00 +09:00
parent cd6a318aa2
commit 4af6eb6ebd
3 changed files with 94 additions and 7 deletions

View File

@@ -4,7 +4,13 @@ import { discoverSkills } from "../../features/opencode-skill-loader"
export async function resolveSkillContent(
skills: string[],
options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider, disabledSkills?: Set<string>, directory?: string }
options: {
gitMasterConfig?: GitMasterConfig
browserProvider?: BrowserAutomationProvider
disabledSkills?: Set<string>
teamModeEnabled?: boolean
directory?: string
}
): Promise<{ content: string | undefined; contents: string[]; error: string | null }> {
if (skills.length === 0) {
return { content: undefined, contents: [], error: null }

View File

@@ -26,11 +26,17 @@ import type { FallbackEntry } from "../../shared/model-requirements"
import { resolveModelForDelegateTask } from "./model-selection"
import { fuzzyMatchModel } from "../../shared/model-availability"
export interface ResolveSubagentExecutionOptions {
allowSisyphusJuniorDirect?: boolean
allowPrimaryAgentDelegation?: boolean
}
export async function resolveSubagentExecution(
args: DelegateTaskArgs,
executorCtx: ExecutorContext,
parentAgent: string | undefined,
categoryExamples: string
categoryExamples: string,
options: ResolveSubagentExecutionOptions = {},
): Promise<{ agentToUse: string; categoryModel: DelegatedModelConfig | undefined; fallbackChain?: FallbackEntry[]; error?: string }> {
const { client, agentOverrides, userCategories } = executorCtx
@@ -40,11 +46,17 @@ export async function resolveSubagentExecution(
const agentName = sanitizeSubagentType(args.subagent_type)
if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) {
if (
!options.allowSisyphusJuniorDirect &&
agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()
) {
const exampleHint = categoryExamples.trim() !== ""
? `Use category parameter instead (e.g., ${categoryExamples}).`
: `Use the category parameter instead (pick one of: quick, deep, ultrabrain, visual-engineering, artistry, writing).`
return {
agentToUse: "",
categoryModel: undefined,
error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. Use category parameter instead (e.g., ${categoryExamples}).
error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. ${exampleHint}
Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`,
}
@@ -73,7 +85,7 @@ Create the work plan directly - that's your job as the planning agent.`,
const mergedAgents = mergeWithClaudeCodeAgents(agents, executorCtx.directory)
const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse)
if (matchedPrimaryAgent) {
if (matchedPrimaryAgent && !options.allowPrimaryAgentDelegation) {
return {
agentToUse: "",
categoryModel: undefined,
@@ -81,7 +93,11 @@ Create the work plan directly - that's your job as the planning agent.`,
}
}
const matchedAgent = findCallableAgentMatch(mergedAgents, agentToUse)
const usePrimary = options.allowPrimaryAgentDelegation && matchedPrimaryAgent !== undefined
const matchedAgent = usePrimary
? matchedPrimaryAgent
: findCallableAgentMatch(mergedAgents, agentToUse)
if (!matchedAgent) {
return {
agentToUse: "",
@@ -90,7 +106,9 @@ Create the work plan directly - that's your job as the planning agent.`,
}
}
agentToUse = stripAgentListSortPrefix(matchedAgent.name)
agentToUse = usePrimary
? matchedAgent.name
: stripAgentListSortPrefix(matchedAgent.name)
const agentConfigKey = getAgentConfigKey(agentToUse)
const agentOverride = agentOverrides?.[agentConfigKey as keyof typeof agentOverrides]

View File

@@ -168,6 +168,69 @@ describe("resolveSubagentExecution", () => {
expect(result.error).toBe('Cannot delegate to primary agent "Prometheus - Plan Builder" via task. Select that agent directly instead.')
})
test("allows delegating to a primary agent when allowPrimaryAgentDelegation is enabled (team-mode path)", async () => {
//#given
readProviderModelsCacheMock.mockReturnValue({
models: { anthropic: ["claude-opus-4-7"] },
connected: ["anthropic"],
updatedAt: "2026-03-03T00:00:00.000Z",
})
const args = createBaseArgs({ subagent_type: "sisyphus" })
const executorCtx = createExecutorContext(async () => ([
{ name: "\u200BSisyphus - Ultraworker", mode: "primary", model: "anthropic/claude-opus-4-7" },
{ name: "oracle", mode: "subagent" },
]))
//#when
const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep", {
allowPrimaryAgentDelegation: true,
})
//#then
expect(result.error).toBeUndefined()
expect(result.agentToUse).toBe("\u200BSisyphus - Ultraworker")
})
test("allows delegating to Sisyphus-Junior when allowSisyphusJuniorDirect is enabled (team-mode path)", async () => {
//#given
readProviderModelsCacheMock.mockReturnValue({
models: { anthropic: ["claude-sonnet-4-6"] },
connected: ["anthropic"],
updatedAt: "2026-03-03T00:00:00.000Z",
})
const args = createBaseArgs({ subagent_type: "sisyphus-junior" })
const executorCtx = createExecutorContext(async () => ([
{ name: "Sisyphus-Junior", mode: "subagent", model: "anthropic/claude-sonnet-4-6" },
{ name: "oracle", mode: "subagent" },
]))
//#when
const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep", {
allowSisyphusJuniorDirect: true,
})
//#then
expect(result.error).toBeUndefined()
expect(result.agentToUse).toBe("Sisyphus-Junior")
})
test("renders a usable fallback hint when categoryExamples is empty for the default Sisyphus-Junior block", async () => {
//#given
const args = createBaseArgs({ subagent_type: "sisyphus-junior" })
const executorCtx = createExecutorContext(async () => ([
{ name: "Sisyphus-Junior", mode: "subagent" },
]))
//#when
const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "")
//#then
expect(result.agentToUse).toBe("")
expect(result.error).toBeDefined()
expect(result.error).not.toContain("(e.g., )")
expect(result.error).toContain("pick one of: quick, deep, ultrabrain")
})
test("requires explicit all or subagent mode for task-callable agents", async () => {
//#given
const args = createBaseArgs({ subagent_type: "custom-worker" })