Merge remote-tracking branch 'origin/dev' into opencode/mighty-wolf
This commit is contained in:
+2
-2
@@ -92,8 +92,8 @@
|
||||
| Category | Model | Domain |
|
||||
|----------|-------|--------|
|
||||
| visual-engineering | gemini-3.1-pro high | Frontend, UI/UX |
|
||||
| ultrabrain | gpt-5.4 xhigh | Hard logic |
|
||||
| deep | gpt-5.4 medium | Autonomous problem-solving |
|
||||
| ultrabrain | gpt-5.5 xhigh | Hard logic |
|
||||
| deep | gpt-5.5 medium | Autonomous problem-solving |
|
||||
| artistry | gemini-3.1-pro high | Creative approaches |
|
||||
| quick | gpt-5.4-mini | Trivial tasks |
|
||||
| unspecified-low | claude-sonnet-4-6 | Moderate effort |
|
||||
|
||||
@@ -395,6 +395,26 @@ describe("executeSync", () => {
|
||||
expect(spawnReservation.commit).toHaveBeenCalledTimes(1)
|
||||
expect(spawnReservation.rollback).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test("strips legacy ZWSP-prefixed agent names from persisted sync prompt body (GH-3259)", async () => {
|
||||
//#given - persisted sync invocation from v3.14.0-v3.16.0 with ZWSP prefix on subagent_type
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies()
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder()
|
||||
const args = {
|
||||
subagent_type: "\u200B\u200BHephaestus - Deep Agent",
|
||||
description: "legacy zwsp",
|
||||
prompt: "find something",
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(recorder.getCapturedInput()?.body.agent).toBe("Hephaestus - Deep Agent")
|
||||
})
|
||||
})
|
||||
|
||||
export {}
|
||||
|
||||
@@ -522,4 +522,48 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
|
||||
expectFn(secondResult).toContain("session_id: ses_second")
|
||||
expectFn(secondResult).not.toContain("interrupt")
|
||||
})
|
||||
|
||||
testFn("strips legacy ZWSP-prefixed agent names from persisted background task launch input (GH-3259)", async () => {
|
||||
//#given - persisted launch input from v3.14.0-v3.16.0 with ZWSP prefix on agent
|
||||
const launchCalls: Array<{ agent: string }> = []
|
||||
const manager = {
|
||||
launch: async (input: { agent: string }) => {
|
||||
launchCalls.push(input)
|
||||
return {
|
||||
id: "bg_legacy_zwsp",
|
||||
sessionID: "ses_legacy_zwsp",
|
||||
description: "Legacy ZWSP",
|
||||
agent: "Hephaestus - Deep Agent",
|
||||
status: "running",
|
||||
}
|
||||
},
|
||||
getTask: () => ({ sessionID: "ses_legacy_zwsp" }),
|
||||
}
|
||||
|
||||
//#when
|
||||
await executeBackgroundTask(
|
||||
{
|
||||
description: "Legacy ZWSP",
|
||||
prompt: "check",
|
||||
run_in_background: true,
|
||||
load_skills: [],
|
||||
},
|
||||
{
|
||||
sessionID: "ses_parent",
|
||||
callID: "call_legacy_zwsp",
|
||||
metadata: async () => {},
|
||||
abort: new AbortController().signal,
|
||||
},
|
||||
{ manager },
|
||||
{ sessionID: "ses_parent", messageID: "msg_legacy_zwsp" },
|
||||
"\u200B\u200BHephaestus - Deep Agent",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
//#then
|
||||
expectFn(launchCalls).toHaveLength(1)
|
||||
expectFn(launchCalls[0].agent).toBe("Hephaestus - Deep Agent")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -97,13 +97,13 @@ If your prompt lacks this structure, REWRITE IT before delegating.
|
||||
export const OPENAI_CATEGORIES: BuiltinCategoryDefinition[] = [
|
||||
{
|
||||
name: "ultrabrain",
|
||||
config: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||
config: { model: "openai/gpt-5.5", variant: "xhigh" },
|
||||
description: "Use ONLY for genuinely hard, logic-heavy tasks. Give clear goals only, not step-by-step instructions.",
|
||||
promptAppend: ULTRABRAIN_CATEGORY_PROMPT_APPEND,
|
||||
},
|
||||
{
|
||||
name: "deep",
|
||||
config: { model: "openai/gpt-5.4", variant: "medium" },
|
||||
config: { model: "openai/gpt-5.5", variant: "medium" },
|
||||
description: "Goal-oriented autonomous problem-solving. Thorough research before action. For hairy problems requiring deep understanding.",
|
||||
promptAppend: DEEP_CATEGORY_PROMPT_APPEND,
|
||||
},
|
||||
|
||||
@@ -33,7 +33,8 @@ const TEST_AVAILABLE_MODELS = new Set([
|
||||
"anthropic/claude-haiku-4-5",
|
||||
"google/gemini-3.1-pro",
|
||||
"google/gemini-3-flash",
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.4-mini",
|
||||
"openai/gpt-5.5",
|
||||
"openai/gpt-5.3-codex",
|
||||
])
|
||||
|
||||
@@ -68,7 +69,7 @@ describe("sisyphus-task", () => {
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
||||
openai: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
openai: ["gpt-5.5", "gpt-5.4-mini", "gpt-5.3-codex"],
|
||||
},
|
||||
connected: ["anthropic", "google", "openai"],
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
@@ -98,7 +99,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// when / #then
|
||||
expect(category).toBeDefined()
|
||||
expect(category.model).toBe("openai/gpt-5.4")
|
||||
expect(category.model).toBe("openai/gpt-5.5")
|
||||
expect(category.variant).toBe("xhigh")
|
||||
})
|
||||
|
||||
@@ -108,7 +109,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// when / #then
|
||||
expect(category).toBeDefined()
|
||||
expect(category.model).toBe("openai/gpt-5.4")
|
||||
expect(category.model).toBe("openai/gpt-5.5")
|
||||
expect(category.variant).toBe("medium")
|
||||
})
|
||||
|
||||
@@ -380,7 +381,7 @@ describe("sisyphus-task", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
await tool.execute(args as unknown as DelegateTaskArgs, toolContext)
|
||||
await tool.execute(args as DelegateTaskArgs, toolContext)
|
||||
|
||||
//#then
|
||||
expect(args.load_skills).toEqual(["playwright", "git-master"])
|
||||
@@ -443,7 +444,7 @@ describe("sisyphus-task", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
await tool.execute(args as unknown as DelegateTaskArgs, toolContext)
|
||||
await tool.execute(args as DelegateTaskArgs, toolContext)
|
||||
|
||||
//#then
|
||||
expect(args.load_skills).toEqual([])
|
||||
@@ -876,7 +877,7 @@ describe("sisyphus-task", () => {
|
||||
const categoryName = "my-custom"
|
||||
const userCategories = {
|
||||
"my-custom": {
|
||||
model: "openai/gpt-5.4",
|
||||
model: "openai/gpt-5.5",
|
||||
temperature: 0.5,
|
||||
prompt_append: "You are a custom agent",
|
||||
},
|
||||
@@ -887,7 +888,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.config.model).toBe("openai/gpt-5.4")
|
||||
expect(result!.config.model).toBe("openai/gpt-5.5")
|
||||
expect(result!.config.temperature).toBe(0.5)
|
||||
expect(result!.promptAppend).toBe("You are a custom agent")
|
||||
})
|
||||
@@ -926,7 +927,7 @@ describe("sisyphus-task", () => {
|
||||
test("systemDefaultModel is used as fallback when custom category has no model", () => {
|
||||
// given - custom category with no model defined
|
||||
const categoryName = "my-custom-no-model"
|
||||
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
||||
const userCategories: Record<string, CategoryConfig> = { "my-custom-no-model": { temperature: 0.5 } }
|
||||
const inheritedModel = "cliproxy/claude-opus-4-7"
|
||||
|
||||
// when
|
||||
@@ -1000,7 +1001,7 @@ describe("sisyphus-task", () => {
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
userCategories: {
|
||||
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||
ultrabrain: { model: "openai/gpt-5.5", variant: "xhigh" },
|
||||
},
|
||||
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||
availableModelsOverride: createTestAvailableModels(),
|
||||
@@ -1028,7 +1029,7 @@ describe("sisyphus-task", () => {
|
||||
// then
|
||||
expect(launchInput.model).toEqual({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
modelID: "gpt-5.5",
|
||||
variant: "xhigh",
|
||||
})
|
||||
})
|
||||
@@ -2447,7 +2448,7 @@ describe("sisyphus-task", () => {
|
||||
},
|
||||
}
|
||||
|
||||
// Use ultrabrain which uses gpt-5.4 (non-gemini)
|
||||
// Use ultrabrain which uses gpt-5.5 (non-gemini)
|
||||
const tool = createDelegateTask({
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
@@ -2553,7 +2554,7 @@ describe("sisyphus-task", () => {
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
||||
openai: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
openai: ["gpt-5.5", "gpt-5.5", "gpt-5.3-codex"],
|
||||
"kimi-for-coding": ["k2p5"],
|
||||
},
|
||||
connected: ["anthropic", "google", "openai", "kimi-for-coding"],
|
||||
@@ -2664,7 +2665,7 @@ describe("sisyphus-task", () => {
|
||||
client: mockClient,
|
||||
userCategories: {
|
||||
"my-unstable-cat": {
|
||||
model: "openai/gpt-5.4",
|
||||
model: "openai/gpt-5.5",
|
||||
is_unstable_agent: true,
|
||||
},
|
||||
},
|
||||
@@ -2746,7 +2747,7 @@ describe("sisyphus-task", () => {
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
// when - using "quick" category which should use "anthropic/claude-haiku-4-5"
|
||||
// when - using "quick" category which should use the catalog model
|
||||
await tool.execute(
|
||||
{
|
||||
description: "Test category fallback",
|
||||
@@ -2758,10 +2759,10 @@ describe("sisyphus-task", () => {
|
||||
toolContext
|
||||
)
|
||||
|
||||
// then - model should be anthropic/claude-haiku-4-5 from DEFAULT_CATEGORIES
|
||||
// then - model should be openai/gpt-5.4-mini from DEFAULT_CATEGORIES
|
||||
// NOT anthropic/claude-sonnet-4-6 (system default)
|
||||
expect(launchInput.model.providerID).toBe("anthropic")
|
||||
expect(launchInput.model.modelID).toBe("claude-haiku-4-5")
|
||||
expect(launchInput.model.providerID).toBe("openai")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.4-mini")
|
||||
})
|
||||
|
||||
test("category delegation ignores UI-selected (Kimi) system default model", async () => {
|
||||
@@ -2811,7 +2812,7 @@ describe("sisyphus-task", () => {
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
// when - using "quick" category which should use "anthropic/claude-haiku-4-5"
|
||||
// when - using "quick" category which should use the catalog model
|
||||
await tool.execute(
|
||||
{
|
||||
description: "UI model inheritance test",
|
||||
@@ -2824,8 +2825,8 @@ describe("sisyphus-task", () => {
|
||||
)
|
||||
|
||||
// then - category model must win (not Kimi)
|
||||
expect(launchInput.model.providerID).toBe("anthropic")
|
||||
expect(launchInput.model.modelID).toBe("claude-haiku-4-5")
|
||||
expect(launchInput.model.providerID).toBe("openai")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.4-mini")
|
||||
})
|
||||
|
||||
test("sisyphus-junior model override takes precedence over category model", async () => {
|
||||
@@ -2872,7 +2873,7 @@ describe("sisyphus-task", () => {
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
// when - using ultrabrain category (default model is openai/gpt-5.4)
|
||||
// when - using ultrabrain category (default model is openai/gpt-5.5)
|
||||
await tool.execute(
|
||||
{
|
||||
description: "Override precedence test",
|
||||
@@ -2924,7 +2925,7 @@ describe("sisyphus-task", () => {
|
||||
client: mockClient,
|
||||
sisyphusJuniorModel: "anthropic/claude-sonnet-4-6",
|
||||
userCategories: {
|
||||
ultrabrain: { model: "openai/gpt-5.4" },
|
||||
ultrabrain: { model: "openai/gpt-5.5" },
|
||||
},
|
||||
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||
availableModelsOverride: createTestAvailableModels(),
|
||||
@@ -2951,7 +2952,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - explicit category model should win
|
||||
expect(launchInput.model.providerID).toBe("openai")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.4")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.5")
|
||||
})
|
||||
|
||||
test("sisyphus-junior model override works with quick category (#1295)", async () => {
|
||||
@@ -3048,7 +3049,7 @@ describe("sisyphus-task", () => {
|
||||
const tool = createDelegateTask({
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
sisyphusJuniorModel: "openai/gpt-5.4",
|
||||
sisyphusJuniorModel: "openai/gpt-5.5",
|
||||
userCategories: {
|
||||
"my-custom": { temperature: 0.5 },
|
||||
},
|
||||
@@ -3075,7 +3076,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - sisyphus-junior override model should be used as fallback
|
||||
expect(launchInput.model.providerID).toBe("openai")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.4")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.5")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3446,7 +3447,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - catalog model is used
|
||||
expect(resolved).not.toBeNull()
|
||||
expect(resolved!.config.model).toBe("openai/gpt-5.4")
|
||||
expect(resolved!.config.model).toBe("openai/gpt-5.5")
|
||||
expect(resolved!.config.variant).toBe("xhigh")
|
||||
})
|
||||
|
||||
@@ -3470,10 +3471,10 @@ describe("sisyphus-task", () => {
|
||||
// when
|
||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
|
||||
// then - category's built-in model wins (ultrabrain uses gpt-5.4)
|
||||
// then - category's built-in model wins (ultrabrain uses gpt-5.5)
|
||||
expect(resolved).not.toBeNull()
|
||||
const actualModel = resolved!.config.model
|
||||
expect(actualModel).toBe("openai/gpt-5.4")
|
||||
expect(actualModel).toBe("openai/gpt-5.5")
|
||||
})
|
||||
|
||||
test("when user defines model - modelInfo should report user-defined regardless of inheritedModel", () => {
|
||||
@@ -3527,18 +3528,18 @@ describe("sisyphus-task", () => {
|
||||
const categoryName = "ultrabrain"
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
|
||||
// when category has a built-in model (gpt-5.4 for ultrabrain)
|
||||
// when category has a built-in model (gpt-5.5 for ultrabrain)
|
||||
const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL })
|
||||
|
||||
// then category's built-in model should be used, NOT inheritedModel
|
||||
expect(resolved).not.toBeNull()
|
||||
expect(resolved!.model).toBe("openai/gpt-5.4")
|
||||
expect(resolved!.model).toBe("openai/gpt-5.5")
|
||||
})
|
||||
|
||||
test("FIXED: systemDefaultModel is used when no userConfig.model and no inheritedModel", () => {
|
||||
// given a custom category with no default model
|
||||
const categoryName = "custom-no-default"
|
||||
const userCategories = { "custom-no-default": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
||||
const userCategories: Record<string, CategoryConfig> = { "custom-no-default": { temperature: 0.5 } }
|
||||
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
// when no inheritedModel is provided, only systemDefaultModel
|
||||
@@ -3588,8 +3589,7 @@ describe("sisyphus-task", () => {
|
||||
test("FIXED: undefined userConfig.model falls back to category built-in model", () => {
|
||||
// given user sets a builtin category but leaves model undefined
|
||||
const categoryName = "visual-engineering"
|
||||
// Using type assertion since we're testing fallback behavior for categories without model
|
||||
const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record<string, CategoryConfig>
|
||||
const userCategories: Record<string, CategoryConfig> = { "visual-engineering": { temperature: 0.2 } }
|
||||
const inheritedModel = "anthropic/claude-opus-4-7"
|
||||
|
||||
// when resolveCategoryConfig is called
|
||||
@@ -3603,8 +3603,7 @@ describe("sisyphus-task", () => {
|
||||
test("systemDefaultModel is used when no other model is available", () => {
|
||||
// given - custom category with no model, but systemDefaultModel is set
|
||||
const categoryName = "my-custom"
|
||||
// Using type assertion since we're testing fallback behavior for categories without model
|
||||
const userCategories = { "my-custom": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
|
||||
const userCategories: Record<string, CategoryConfig> = { "my-custom": { temperature: 0.5 } }
|
||||
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
// when
|
||||
@@ -3935,7 +3934,7 @@ describe("sisyphus-task", () => {
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.5" } },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -4002,7 +4001,7 @@ describe("sisyphus-task", () => {
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.5" } },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -4111,11 +4110,11 @@ describe("sisyphus-task", () => {
|
||||
)
|
||||
|
||||
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
|
||||
// oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-7 (anthropic)
|
||||
// Since openai is in connectedProviders, should resolve to openai/gpt-5.4
|
||||
// oracle fallback chain: gpt-5.5 (openai) > gemini-3.1-pro (google) > claude-opus-4-7 (anthropic)
|
||||
// Since openai is in connectedProviders, should resolve to openai/gpt-5.5
|
||||
expect(promptBody.model).toBeDefined()
|
||||
expect(promptBody.model.providerID).toBe("openai")
|
||||
expect(promptBody.model.modelID).toContain("gpt-5.4")
|
||||
expect(promptBody.model.modelID).toContain("gpt-5.5")
|
||||
}, { timeout: 20000 })
|
||||
})
|
||||
|
||||
|
||||
@@ -963,4 +963,24 @@ describe("resolveSubagentExecution - agent name sanitization", () => {
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.agentToUse).toBe("Sisyphus - Ultraworker")
|
||||
})
|
||||
|
||||
test("strips legacy ZWSP-prefixed agent names from persisted subagent runtime state (GH-3259)", async () => {
|
||||
//#given - persisted runtime agent metadata from v3.14.0-v3.16.0 with ZWSP prefix
|
||||
readProviderModelsCacheMock.mockReturnValue({
|
||||
models: {},
|
||||
connected: [],
|
||||
updatedAt: "2026-03-03T00:00:00.000Z",
|
||||
})
|
||||
const args = createBaseArgs({ subagent_type: "Hephaestus - Deep Agent" })
|
||||
const executorCtx = createExecutorContext(async () => ([
|
||||
{ name: "\u200B\u200BHephaestus - Deep Agent", mode: "subagent", model: "openai/gpt-5.3-codex" },
|
||||
]))
|
||||
|
||||
//#when
|
||||
const result = await resolveSubagentExecution(args, executorCtx, "oracle", "deep")
|
||||
|
||||
//#then
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.agentToUse).toBe("Hephaestus - Deep Agent")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("buildMultimodalLookerFallbackChain", () => {
|
||||
it("preserves hardcoded variant metadata for cache-derived entries", async () => {
|
||||
// given
|
||||
const { buildMultimodalLookerFallbackChain } = await import("./multimodal-fallback-chain")
|
||||
const visionCapableModels = [{ providerID: "openai", modelID: "gpt-5.4" }]
|
||||
const visionCapableModels = [{ providerID: "openai", modelID: "gpt-5.5" }]
|
||||
|
||||
// when
|
||||
const result = buildMultimodalLookerFallbackChain(visionCapableModels)
|
||||
@@ -42,7 +42,7 @@ describe("buildMultimodalLookerFallbackChain", () => {
|
||||
// then
|
||||
expect(result[0]).toEqual({
|
||||
providers: ["openai"],
|
||||
model: "gpt-5.4",
|
||||
model: "gpt-5.5",
|
||||
variant: "medium",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,13 +4,10 @@ import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:te
|
||||
import type { ToolContext } from "@opencode-ai/plugin/tool"
|
||||
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
|
||||
import * as skillContent from "../../features/opencode-skill-loader/skill-content"
|
||||
import * as commandDiscovery from "../slashcommand/command-discovery"
|
||||
|
||||
const discoverCommandsSync = mock(() => [])
|
||||
|
||||
mock.module("../slashcommand/command-discovery", () => ({
|
||||
discoverCommandsSync,
|
||||
}))
|
||||
|
||||
function createMockSkill(name: string): LoadedSkill {
|
||||
return {
|
||||
name,
|
||||
@@ -49,7 +46,13 @@ function createMockContext(sessionID: string): ToolContext {
|
||||
}
|
||||
}
|
||||
|
||||
async function createSkillTool(...args: Parameters<typeof import("./tools").createSkillTool>): ReturnType<typeof import("./tools").createSkillTool> {
|
||||
const module = await import(`./tools?test=${Date.now()}-${Math.random()}`)
|
||||
return module.createSkillTool(...args)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(commandDiscovery, "discoverCommandsSync").mockImplementation(discoverCommandsSync)
|
||||
spyOn(skillContent, "getAllSkills").mockImplementation(getAllSkills)
|
||||
spyOn(skillContent, "clearSkillCache").mockImplementation(clearSkillCache)
|
||||
})
|
||||
@@ -65,8 +68,7 @@ describe("createSkillTool", () => {
|
||||
const baselineDiscoverCommandsSyncCalls = discoverCommandsSync.mock.calls.length
|
||||
|
||||
// when
|
||||
const { createSkillTool } = await import("./tools")
|
||||
const skillTool = createSkillTool({})
|
||||
const skillTool = await createSkillTool({})
|
||||
|
||||
// then
|
||||
expect(discoverCommandsSync.mock.calls.length).toBe(baselineDiscoverCommandsSyncCalls)
|
||||
@@ -82,8 +84,7 @@ describe("createSkillTool", () => {
|
||||
const baselineGetAllSkillsCalls = getAllSkills.mock.calls.length
|
||||
|
||||
// when
|
||||
const { createSkillTool } = await import("./tools")
|
||||
const skillTool = createSkillTool({})
|
||||
const skillTool = await createSkillTool({})
|
||||
|
||||
// then
|
||||
expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls)
|
||||
@@ -99,8 +100,7 @@ describe("createSkillTool", () => {
|
||||
const sessionContext = createMockContext("session-clear-once")
|
||||
|
||||
// when
|
||||
const { createSkillTool } = await import("./tools")
|
||||
const skillTool = createSkillTool({})
|
||||
const skillTool = await createSkillTool({})
|
||||
void skillTool.description
|
||||
await flushMicrotasks()
|
||||
await skillTool.execute({ name: "lazy-skill" }, sessionContext)
|
||||
@@ -116,8 +116,7 @@ describe("createSkillTool", () => {
|
||||
const baselineGetAllSkillsCalls = getAllSkills.mock.calls.length
|
||||
const sessionAContext = createMockContext("session-a")
|
||||
const sessionBContext = createMockContext("session-b")
|
||||
const { createSkillTool } = await import("./tools")
|
||||
const skillTool = createSkillTool({})
|
||||
const skillTool = await createSkillTool({})
|
||||
|
||||
// when
|
||||
await skillTool.execute({ name: "lazy-skill" }, sessionAContext)
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { SkillArgs, SkillLoadOptions } from "./types"
|
||||
import type { LoadedSkill } from "../../features/opencode-skill-loader"
|
||||
import { clearSkillCache, getAllSkills } from "../../features/opencode-skill-loader/skill-content"
|
||||
import { injectGitMasterConfig } from "../../features/opencode-skill-loader/skill-content"
|
||||
import { discoverCommandsSync } from "../slashcommand/command-discovery"
|
||||
import * as commandDiscovery from "../slashcommand/command-discovery"
|
||||
import type { CommandInfo } from "../slashcommand/types"
|
||||
import { formatLoadedCommand } from "../slashcommand/command-output-formatter"
|
||||
import { formatCombinedDescription } from "./description-formatter"
|
||||
@@ -37,14 +37,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
|
||||
disabledSkills: options?.disabledSkills,
|
||||
browserProvider: options?.browserProvider,
|
||||
})) ?? []
|
||||
const allSkills = !options.skills
|
||||
? discovered
|
||||
: [
|
||||
...discovered,
|
||||
...options.skills.filter(
|
||||
(skill) => !new Set(discovered.map((discoveredSkill) => discoveredSkill.name)).has(skill.name)
|
||||
),
|
||||
]
|
||||
const allSkills = options.skills ? [...options.skills] : discovered
|
||||
|
||||
if (options.nativeSkills) {
|
||||
try {
|
||||
@@ -58,7 +51,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
|
||||
}
|
||||
|
||||
const getCommands = (): CommandInfo[] => {
|
||||
return discoverCommandsSync(undefined, {
|
||||
return commandDiscovery.discoverCommandsSync(undefined, {
|
||||
pluginsEnabled: options.pluginsEnabled,
|
||||
enabledPluginsOverride: options.enabledPluginsOverride,
|
||||
}) ?? []
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export { EXCLUDED_DIRS } from "../../shared/excluded-dirs"
|
||||
export { parseFrontmatter } from "../../shared/frontmatter"
|
||||
export { sanitizeModelField } from "../../shared/model-sanitizer"
|
||||
export { getOpenCodeCommandDirs } from "../../shared/opencode-command-dirs"
|
||||
export { discoverPluginCommandDefinitions } from "../../shared/plugin-command-discovery"
|
||||
export { findProjectOpencodeCommandDirs } from "../../shared/project-discovery-dirs"
|
||||
@@ -7,11 +7,12 @@ import {
|
||||
getOpenCodeCommandDirs,
|
||||
discoverPluginCommandDefinitions,
|
||||
EXCLUDED_DIRS,
|
||||
} from "../../shared"
|
||||
} from "./command-discovery-deps"
|
||||
import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types"
|
||||
import { isMarkdownFile } from "../../shared/file-utils"
|
||||
import { getClaudeConfigDir, log } from "../../shared"
|
||||
import { loadBuiltinCommands } from "../../features/builtin-commands"
|
||||
import { getClaudeConfigDir } from "../../shared/claude-config-dir"
|
||||
import { log } from "../../shared/logger"
|
||||
import { loadBuiltinCommands } from "../../features/builtin-commands/commands"
|
||||
import type { CommandInfo, CommandMetadata, CommandScope } from "./types"
|
||||
|
||||
export interface CommandDiscoveryOptions {
|
||||
|
||||
Reference in New Issue
Block a user