refactor(tools/delegate-task): enhance skill resolution and type safety
- Add improved type definitions for skill resolution
- Enhance executor with better type safety for delegation flows
- Add comprehensive test coverage for delegation tool behavior
- Improve code organization for skill resolver integration
🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
parent
7788ba3d8a
commit
bdaa8fc6c1
@ -14,7 +14,7 @@ import { getTaskToastManager } from "../../features/task-toast-manager"
|
|||||||
import { subagentSessions, getSessionAgent } from "../../features/claude-code-session-state"
|
import { subagentSessions, getSessionAgent } from "../../features/claude-code-session-state"
|
||||||
import { log, getAgentToolRestrictions, resolveModelPipeline, promptWithModelSuggestionRetry, promptSyncWithModelSuggestionRetry } from "../../shared"
|
import { log, getAgentToolRestrictions, resolveModelPipeline, promptWithModelSuggestionRetry, promptSyncWithModelSuggestionRetry } from "../../shared"
|
||||||
import { fetchAvailableModels, isModelAvailable } from "../../shared/model-availability"
|
import { fetchAvailableModels, isModelAvailable } from "../../shared/model-availability"
|
||||||
import { readConnectedProvidersCache } from "../../shared/connected-providers-cache"
|
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
|
||||||
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
||||||
import { storeToolMetadata } from "../../features/tool-metadata-store"
|
import { storeToolMetadata } from "../../features/tool-metadata-store"
|
||||||
|
|
||||||
@ -40,6 +40,8 @@ export interface ExecutorContext {
|
|||||||
manager: BackgroundManager
|
manager: BackgroundManager
|
||||||
client: OpencodeClient
|
client: OpencodeClient
|
||||||
directory: string
|
directory: string
|
||||||
|
connectedProvidersOverride?: string[] | null
|
||||||
|
availableModelsOverride?: Set<string>
|
||||||
userCategories?: CategoriesConfig
|
userCategories?: CategoriesConfig
|
||||||
gitMasterConfig?: GitMasterConfig
|
gitMasterConfig?: GitMasterConfig
|
||||||
sisyphusJuniorModel?: string
|
sisyphusJuniorModel?: string
|
||||||
@ -727,10 +729,15 @@ export async function resolveCategoryExecution(
|
|||||||
): Promise<CategoryResolutionResult> {
|
): Promise<CategoryResolutionResult> {
|
||||||
const { client, userCategories, sisyphusJuniorModel } = executorCtx
|
const { client, userCategories, sisyphusJuniorModel } = executorCtx
|
||||||
|
|
||||||
const connectedProviders = readConnectedProvidersCache()
|
const connectedProviders = executorCtx.connectedProvidersOverride !== undefined
|
||||||
const availableModels = await fetchAvailableModels(client, {
|
? executorCtx.connectedProvidersOverride
|
||||||
connectedProviders: connectedProviders ?? undefined,
|
: connectedProvidersCache.readConnectedProvidersCache()
|
||||||
})
|
|
||||||
|
const availableModels = executorCtx.availableModelsOverride !== undefined
|
||||||
|
? executorCtx.availableModelsOverride
|
||||||
|
: await fetchAvailableModels(client, {
|
||||||
|
connectedProviders: connectedProviders ?? undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const resolved = resolveCategoryConfig(args.category!, {
|
const resolved = resolveCategoryConfig(args.category!, {
|
||||||
userCategories,
|
userCategories,
|
||||||
@ -775,7 +782,7 @@ export async function resolveCategoryExecution(
|
|||||||
userModel: explicitCategoryModel ?? overrideModel,
|
userModel: explicitCategoryModel ?? overrideModel,
|
||||||
categoryDefaultModel: resolved.model,
|
categoryDefaultModel: resolved.model,
|
||||||
},
|
},
|
||||||
constraints: { availableModels },
|
constraints: { availableModels, connectedProviders },
|
||||||
policy: {
|
policy: {
|
||||||
fallbackChain: requirement.fallbackChain,
|
fallbackChain: requirement.fallbackChain,
|
||||||
systemDefaultModel,
|
systemDefaultModel,
|
||||||
@ -941,26 +948,31 @@ Create the work plan directly - that's your job as the planning agent.`,
|
|||||||
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentNameLower]
|
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentNameLower]
|
||||||
|
|
||||||
if (agentOverride?.model || agentRequirement) {
|
if (agentOverride?.model || agentRequirement) {
|
||||||
const connectedProviders = readConnectedProvidersCache()
|
const connectedProviders = executorCtx.connectedProvidersOverride !== undefined
|
||||||
const availableModels = await fetchAvailableModels(client, {
|
? executorCtx.connectedProvidersOverride
|
||||||
connectedProviders: connectedProviders ?? undefined,
|
: connectedProvidersCache.readConnectedProvidersCache()
|
||||||
})
|
|
||||||
|
const availableModels = executorCtx.availableModelsOverride !== undefined
|
||||||
|
? executorCtx.availableModelsOverride
|
||||||
|
: await fetchAvailableModels(client, {
|
||||||
|
connectedProviders: connectedProviders ?? undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const matchedAgentModelStr = matchedAgent.model
|
const matchedAgentModelStr = matchedAgent.model
|
||||||
? `${matchedAgent.model.providerID}/${matchedAgent.model.modelID}`
|
? `${matchedAgent.model.providerID}/${matchedAgent.model.modelID}`
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const resolution = resolveModelPipeline({
|
const resolution = resolveModelPipeline({
|
||||||
intent: {
|
intent: {
|
||||||
userModel: agentOverride?.model,
|
userModel: agentOverride?.model,
|
||||||
categoryDefaultModel: matchedAgentModelStr,
|
categoryDefaultModel: matchedAgentModelStr,
|
||||||
},
|
},
|
||||||
constraints: { availableModels },
|
constraints: { availableModels, connectedProviders },
|
||||||
policy: {
|
policy: {
|
||||||
fallbackChain: agentRequirement?.fallbackChain,
|
fallbackChain: agentRequirement?.fallbackChain,
|
||||||
systemDefaultModel: undefined,
|
systemDefaultModel: undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (resolution) {
|
if (resolution) {
|
||||||
const parsed = parseModelString(resolution.model)
|
const parsed = parseModelString(resolution.model)
|
||||||
|
|||||||
@ -10,6 +10,21 @@ import * as connectedProvidersCache from "../../shared/connected-providers-cache
|
|||||||
|
|
||||||
const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-5"
|
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",
|
||||||
|
])
|
||||||
|
|
||||||
|
function createTestAvailableModels(): Set<string> {
|
||||||
|
return new Set(TEST_AVAILABLE_MODELS)
|
||||||
|
}
|
||||||
|
|
||||||
describe("sisyphus-task", () => {
|
describe("sisyphus-task", () => {
|
||||||
let cacheSpy: ReturnType<typeof spyOn>
|
let cacheSpy: ReturnType<typeof spyOn>
|
||||||
let providerModelsSpy: ReturnType<typeof spyOn>
|
let providerModelsSpy: ReturnType<typeof spyOn>
|
||||||
@ -271,6 +286,8 @@ describe("sisyphus-task", () => {
|
|||||||
const tool = createDelegateTask({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -324,6 +341,8 @@ describe("sisyphus-task", () => {
|
|||||||
const tool = createDelegateTask({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -436,6 +455,8 @@ describe("sisyphus-task", () => {
|
|||||||
const tool = createDelegateTask({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataCalls: Array<{ title?: string; metadata?: Record<string, unknown> }> = []
|
const metadataCalls: Array<{ title?: string; metadata?: Record<string, unknown> }> = []
|
||||||
@ -727,6 +748,8 @@ describe("sisyphus-task", () => {
|
|||||||
userCategories: {
|
userCategories: {
|
||||||
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
||||||
},
|
},
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -790,6 +813,8 @@ describe("sisyphus-task", () => {
|
|||||||
const tool = createDelegateTask({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -1950,6 +1975,8 @@ describe("sisyphus-task", () => {
|
|||||||
client: mockClient,
|
client: mockClient,
|
||||||
// userCategories: undefined - use DEFAULT_CATEGORIES only
|
// userCategories: undefined - use DEFAULT_CATEGORIES only
|
||||||
// sisyphusJuniorModel: undefined
|
// sisyphusJuniorModel: undefined
|
||||||
|
connectedProvidersOverride: null,
|
||||||
|
availableModelsOverride: new Set(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -2013,6 +2040,8 @@ describe("sisyphus-task", () => {
|
|||||||
userCategories: {
|
userCategories: {
|
||||||
"fallback-test": { model: "anthropic/claude-opus-4-6" },
|
"fallback-test": { model: "anthropic/claude-opus-4-6" },
|
||||||
},
|
},
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -2072,6 +2101,8 @@ describe("sisyphus-task", () => {
|
|||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
|
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -2135,6 +2166,8 @@ describe("sisyphus-task", () => {
|
|||||||
userCategories: {
|
userCategories: {
|
||||||
ultrabrain: { model: "openai/gpt-5.3-codex" },
|
ultrabrain: { model: "openai/gpt-5.3-codex" },
|
||||||
},
|
},
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -2194,6 +2227,8 @@ describe("sisyphus-task", () => {
|
|||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
|
sisyphusJuniorModel: "anthropic/claude-sonnet-4-5",
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
@ -3207,6 +3242,8 @@ describe("sisyphus-task", () => {
|
|||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
// no agentOverrides
|
// no agentOverrides
|
||||||
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolContext = {
|
const toolContext = {
|
||||||
|
|||||||
@ -50,6 +50,15 @@ export interface DelegateTaskToolOptions {
|
|||||||
manager: BackgroundManager
|
manager: BackgroundManager
|
||||||
client: OpencodeClient
|
client: OpencodeClient
|
||||||
directory: string
|
directory: string
|
||||||
|
/**
|
||||||
|
* Test hook: bypass global cache reads (Bun runs tests in parallel).
|
||||||
|
* If provided, resolveCategoryExecution/resolveSubagentExecution uses this instead of reading from disk cache.
|
||||||
|
*/
|
||||||
|
connectedProvidersOverride?: string[] | null
|
||||||
|
/**
|
||||||
|
* Test hook: bypass fetchAvailableModels() by providing an explicit available model set.
|
||||||
|
*/
|
||||||
|
availableModelsOverride?: Set<string>
|
||||||
userCategories?: CategoriesConfig
|
userCategories?: CategoriesConfig
|
||||||
gitMasterConfig?: GitMasterConfig
|
gitMasterConfig?: GitMasterConfig
|
||||||
sisyphusJuniorModel?: string
|
sisyphusJuniorModel?: string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user