mirror of
https://mirror.skon.top/github.com/code-yeongyu/oh-my-opencode
synced 2026-04-30 18:50:29 +08:00
feat(tools): wire team-mode into skill and subagent resolvers
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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" })
|
||||
|
||||
Reference in New Issue
Block a user