d8f89b6965
The shared resolveCategoryExecution path treats `agents.sisyphus-junior.model` (plugin config) as a higher-precedence override than the category default. That ranking is correct for plain `task(category=...)` delegations - it lets users pin sisyphus-junior to their preferred general model - but in team-mode it collapses every kind:"category" member onto the same model. Hyperplan was the visible victim: skeptic/validator/researcher/architect/creative are routed through sisyphus-junior with five distinct categories (unspecified-low / unspecified-high / deep / ultrabrain / artistry). With any sisyphus-junior model configured, all five resolved to that single override model instead of their category defaults, defeating the multi-model adversarial debate. Strip the override at the team-mode boundary in resolveMember rather than changing resolveCategoryExecution itself, so: - delegate-task callers keep the existing override semantics (3 regression tests at tools.test.ts:2832, 2958, 3020 stay green) - per-category user overrides (`categories[X].model`) and explicit fallback chains continue to apply - only the `kind:"category"` team-mode path opts out of the global override Lock the contract with a regression test in resolve-member.test.ts. Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
import type { FallbackEntry } from "../../../shared/model-requirements"
|
|
import type { DelegatedModelConfig } from "../../../shared/model-resolution-types"
|
|
import type { ExecutorContext } from "../../../tools/delegate-task/executor-types"
|
|
import type { DelegateTaskArgs } from "../../../tools/delegate-task/types"
|
|
import type { Member } from "../types"
|
|
import {
|
|
buildSystemContent,
|
|
resolveCategoryExecution,
|
|
resolveSubagentExecution,
|
|
} from "./resolve-member-dependencies"
|
|
|
|
export class TeamMemberResolutionError extends Error {
|
|
constructor(public readonly memberName: string, public readonly cause: Error) {
|
|
super(`Failed to resolve member '${memberName}': ${cause.message}`)
|
|
this.name = "TeamMemberResolutionError"
|
|
}
|
|
}
|
|
|
|
export interface ResolvedMember {
|
|
memberName: string
|
|
agentToUse: string
|
|
model: DelegatedModelConfig | undefined
|
|
fallbackChain: FallbackEntry[] | undefined
|
|
systemContent: string
|
|
}
|
|
|
|
function createBaseDelegateTaskArgs(prompt: string): Pick<DelegateTaskArgs, "description" | "load_skills" | "prompt" | "run_in_background"> {
|
|
return {
|
|
description: "Resolve team member",
|
|
load_skills: [],
|
|
prompt,
|
|
run_in_background: false,
|
|
}
|
|
}
|
|
|
|
function normalizeResolutionError(error: unknown): Error {
|
|
return error instanceof Error ? error : new Error(String(error))
|
|
}
|
|
|
|
function resolveSystemContent(input: {
|
|
agentToUse: string
|
|
categoryPromptAppend?: string
|
|
maxPromptTokens?: number
|
|
model: DelegatedModelConfig | undefined
|
|
}): string {
|
|
return buildSystemContent({
|
|
agentName: input.agentToUse,
|
|
categoryPromptAppend: input.categoryPromptAppend,
|
|
maxPromptTokens: input.maxPromptTokens,
|
|
model: input.model,
|
|
}) ?? ""
|
|
}
|
|
|
|
// Strip global `agents.sisyphus-junior.model` override at the team-mode boundary —
|
|
// `resolveCategoryExecution` ranks it above category defaults (correct for plain
|
|
// `task(category=…)`, wrong here) and would collapse every team member to the same model.
|
|
function withoutSisyphusJuniorOverride(ctx: ExecutorContext): ExecutorContext {
|
|
if (ctx.sisyphusJuniorModel === undefined) return ctx
|
|
return { ...ctx, sisyphusJuniorModel: undefined }
|
|
}
|
|
|
|
export async function resolveMember(
|
|
member: Member,
|
|
ctx: ExecutorContext,
|
|
categoryExamples: string,
|
|
parentAgent?: string,
|
|
): Promise<ResolvedMember> {
|
|
try {
|
|
if (member.kind === "category") {
|
|
const execution = await resolveCategoryExecution(
|
|
{
|
|
...createBaseDelegateTaskArgs(member.prompt),
|
|
category: member.category,
|
|
subagent_type: "sisyphus-junior",
|
|
},
|
|
withoutSisyphusJuniorOverride(ctx),
|
|
undefined,
|
|
undefined,
|
|
)
|
|
|
|
if (execution.error) {
|
|
throw new Error(execution.error)
|
|
}
|
|
|
|
return {
|
|
memberName: member.name,
|
|
agentToUse: execution.agentToUse,
|
|
model: execution.categoryModel,
|
|
fallbackChain: execution.fallbackChain,
|
|
systemContent: resolveSystemContent({
|
|
agentToUse: execution.agentToUse,
|
|
categoryPromptAppend: execution.categoryPromptAppend,
|
|
maxPromptTokens: execution.maxPromptTokens,
|
|
model: execution.categoryModel,
|
|
}),
|
|
}
|
|
}
|
|
|
|
const execution = await resolveSubagentExecution(
|
|
{
|
|
...createBaseDelegateTaskArgs(member.prompt ?? ""),
|
|
subagent_type: member.subagent_type,
|
|
},
|
|
ctx,
|
|
parentAgent,
|
|
categoryExamples,
|
|
{
|
|
allowSisyphusJuniorDirect: true,
|
|
allowPrimaryAgentDelegation: true,
|
|
},
|
|
)
|
|
|
|
if (execution.error) {
|
|
throw new Error(execution.error)
|
|
}
|
|
|
|
return {
|
|
memberName: member.name,
|
|
agentToUse: execution.agentToUse,
|
|
model: execution.categoryModel,
|
|
fallbackChain: execution.fallbackChain,
|
|
systemContent: resolveSystemContent({
|
|
agentToUse: execution.agentToUse,
|
|
model: execution.categoryModel,
|
|
}),
|
|
}
|
|
} catch (error) {
|
|
throw new TeamMemberResolutionError(member.name, normalizeResolutionError(error))
|
|
}
|
|
}
|