fix(agents): include custom agents in orchestrator delegation prompt (#1623)
This commit is contained in:
parent
71ac54c33e
commit
f035be842d
28
bun.lock
28
bun.lock
@ -28,13 +28,13 @@
|
|||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"oh-my-opencode-darwin-arm64": "3.3.0",
|
"oh-my-opencode-darwin-arm64": "3.3.1",
|
||||||
"oh-my-opencode-darwin-x64": "3.3.0",
|
"oh-my-opencode-darwin-x64": "3.3.1",
|
||||||
"oh-my-opencode-linux-arm64": "3.3.0",
|
"oh-my-opencode-linux-arm64": "3.3.1",
|
||||||
"oh-my-opencode-linux-arm64-musl": "3.3.0",
|
"oh-my-opencode-linux-arm64-musl": "3.3.1",
|
||||||
"oh-my-opencode-linux-x64": "3.3.0",
|
"oh-my-opencode-linux-x64": "3.3.1",
|
||||||
"oh-my-opencode-linux-x64-musl": "3.3.0",
|
"oh-my-opencode-linux-x64-musl": "3.3.1",
|
||||||
"oh-my-opencode-windows-x64": "3.3.0",
|
"oh-my-opencode-windows-x64": "3.3.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -226,19 +226,19 @@
|
|||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
|
|
||||||
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.3.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-P2kZKJqZaA4j0qtGM3I8+ZeH204ai27ni/OXLjtFdOewRjJgrahxaC1XslgK7q/KU9fXz6BQfEqAjbvyPf/rgQ=="],
|
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.3.1", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-R+o42Km6bsIaW6D3I8uu2HCF3BjIWqa/fg38W5y4hJEOw4mL0Q7uV4R+0vtrXRHo9crXTK9ag0fqVQUm+Y6iAQ=="],
|
||||||
|
|
||||||
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.3.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-RopOorbW1WyhMQJ+ipuqiOA1GICS+3IkOwNyEe0KZlCLpoEDTyFopIL87HSns+gEQPMxnknroDp8lzxn1AKgjw=="],
|
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.3.1", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-7VTbpR1vH3OEkoJxBKtYuxFPX8M3IbJKoeHWME9iK6FpT11W1ASsjyuhvzB1jcxSeqF8ddMnjitlG5ub6h5EVw=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.3.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-297iEfuK+05g+q64crPW78Zbgm/j5PGjDDweSPkZ6rI6SEfHMvOIkGxMvN8gugM3zcH8FOCQXoY2nC8b6x3pwQ=="],
|
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.3.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-BZ/r/CFlvbOxkdZZrRoT16xFOjibRZHuwQnaE4f0JvOzgK6/HWp3zJI1+2/aX/oK5GA6lZxNWRrJC/SKUi8LEg=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.3.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-oVxP0+yn66HQYfrl9QT6I7TumRzciuPB4z24+PwKEVcDjPbWXQqLY1gwOGHZAQBPLf0vwewv9ybEDVD42RRH4g=="],
|
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.3.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-U90Wruf21h+CJbtcrS7MeTAc/5VOF6RI+5jr7qj/cCxjXNJtjhyJdz/maehArjtgf304+lYCM/Mh1i+G2D3YFQ=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.3.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-k9LoLkisLJwJNR1J0Bh1bjGtGBkl5D9WzFPSdZCAlyiT6TgG9w5erPTlXqtl2Lt0We5tYUVYlkEIHRMK/ugNsQ=="],
|
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.3.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-sYzohSNdwsAhivbXcbhPdF1qqQi2CCI7FSgbmvvfBOMyZ8HAgqOFqYW2r3GPdmtywzkjOTvCzTG56FZwEjx15w=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.3.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-7asXCeae7wBxJrzoZ7J6Yo1oaOxwUN3bTO7jWurCTMs5TDHO+pEHysgv/nuF1jvj1T+r1vg1H5ZmopuKy1qvXg=="],
|
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.3.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-aG5pZ4eWS0YSGUicOnjMkUPrIqQV4poYF+d9SIvrfvlaMcK6WlQn7jXzgNCwJsfGn5lyhSmjshZBEU+v79Ua3w=="],
|
||||||
|
|
||||||
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.3.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-ABvwfaXb2xdrpbivzlPPJzIm5vXp+QlVakkaHEQf3TU6Mi/+fehH6Qhq/KMh66FDO2gq3xmxbH7nktHRQp9kNA=="],
|
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.3.1", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-FGH7cnzBqNwjSkzCDglMsVttaq+MsykAxa7ehaFK+0dnBZArvllS3W13a3dGaANHMZzfK0vz8hNDUdVi7Z63cA=="],
|
||||||
|
|
||||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { AgentPromptMetadata, BuiltinAgentName } from "./types"
|
import type { AgentPromptMetadata } from "./types"
|
||||||
import { truncateDescription } from "../shared/truncate-description"
|
import { truncateDescription } from "../shared/truncate-description"
|
||||||
|
|
||||||
export interface AvailableAgent {
|
export interface AvailableAgent {
|
||||||
name: BuiltinAgentName
|
name: string
|
||||||
description: string
|
description: string
|
||||||
metadata: AgentPromptMetadata
|
metadata: AgentPromptMetadata
|
||||||
}
|
}
|
||||||
|
|||||||
@ -249,6 +249,56 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
expect(agents.sisyphus.prompt).toContain("frontend-ui-ux")
|
expect(agents.sisyphus.prompt).toContain("frontend-ui-ux")
|
||||||
expect(agents.sisyphus.prompt).toContain("git-master")
|
expect(agents.sisyphus.prompt).toContain("git-master")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("includes custom agents from OpenCode registry in orchestrator prompts", async () => {
|
||||||
|
// #given
|
||||||
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||||
|
new Set([
|
||||||
|
"anthropic/claude-opus-4-6",
|
||||||
|
"kimi-for-coding/k2p5",
|
||||||
|
"opencode/kimi-k2.5-free",
|
||||||
|
"zai-coding-plan/glm-4.7",
|
||||||
|
"opencode/glm-4.7-free",
|
||||||
|
"openai/gpt-5.2",
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
const client = {
|
||||||
|
agent: {
|
||||||
|
list: async () => ({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: "researcher",
|
||||||
|
description: "Research agent for deep analysis",
|
||||||
|
mode: "subagent",
|
||||||
|
hidden: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// #when
|
||||||
|
const agents = await createBuiltinAgents(
|
||||||
|
[],
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
TEST_DEFAULT_MODEL,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
client
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.sisyphus.prompt).toContain("researcher")
|
||||||
|
expect(agents.hephaestus.prompt).toContain("researcher")
|
||||||
|
expect(agents.atlas.prompt).toContain("researcher")
|
||||||
|
} finally {
|
||||||
|
fetchSpy.mockRestore()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("createBuiltinAgents without systemDefaultModel", () => {
|
describe("createBuiltinAgents without systemDefaultModel", () => {
|
||||||
|
|||||||
@ -11,7 +11,18 @@ import { createAtlasAgent, atlasPromptMetadata } from "./atlas"
|
|||||||
import { createMomusAgent, momusPromptMetadata } from "./momus"
|
import { createMomusAgent, momusPromptMetadata } from "./momus"
|
||||||
import { createHephaestusAgent } from "./hephaestus"
|
import { createHephaestusAgent } from "./hephaestus"
|
||||||
import type { AvailableAgent, AvailableCategory, AvailableSkill } from "./dynamic-agent-prompt-builder"
|
import type { AvailableAgent, AvailableCategory, AvailableSkill } from "./dynamic-agent-prompt-builder"
|
||||||
import { deepMerge, fetchAvailableModels, resolveModelPipeline, AGENT_MODEL_REQUIREMENTS, readConnectedProvidersCache, isModelAvailable, isAnyFallbackModelAvailable, isAnyProviderConnected, migrateAgentConfig } from "../shared"
|
import {
|
||||||
|
deepMerge,
|
||||||
|
fetchAvailableModels,
|
||||||
|
resolveModelPipeline,
|
||||||
|
AGENT_MODEL_REQUIREMENTS,
|
||||||
|
readConnectedProvidersCache,
|
||||||
|
isModelAvailable,
|
||||||
|
isAnyFallbackModelAvailable,
|
||||||
|
isAnyProviderConnected,
|
||||||
|
migrateAgentConfig,
|
||||||
|
truncateDescription,
|
||||||
|
} from "../shared"
|
||||||
import { DEFAULT_CATEGORIES, CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants"
|
import { DEFAULT_CATEGORIES, CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants"
|
||||||
import { resolveMultipleSkills } from "../features/opencode-skill-loader/skill-content"
|
import { resolveMultipleSkills } from "../features/opencode-skill-loader/skill-content"
|
||||||
import { createBuiltinSkills } from "../features/builtin-skills"
|
import { createBuiltinSkills } from "../features/builtin-skills"
|
||||||
@ -52,6 +63,65 @@ function isFactory(source: AgentSource): source is AgentFactory {
|
|||||||
return typeof source === "function"
|
return typeof source === "function"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegisteredAgentSummary = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRegisteredAgentSummaries(input: unknown): RegisteredAgentSummary[] {
|
||||||
|
if (!Array.isArray(input)) return []
|
||||||
|
|
||||||
|
const result: RegisteredAgentSummary[] = []
|
||||||
|
for (const item of input) {
|
||||||
|
if (!isRecord(item)) continue
|
||||||
|
|
||||||
|
const name = typeof item.name === "string" ? item.name : undefined
|
||||||
|
if (!name) continue
|
||||||
|
|
||||||
|
const hidden = item.hidden
|
||||||
|
if (hidden === true) continue
|
||||||
|
|
||||||
|
const description = typeof item.description === "string" ? item.description : ""
|
||||||
|
result.push({ name, description })
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRegisteredAgentsFromClient(client: unknown): Promise<RegisteredAgentSummary[]> {
|
||||||
|
if (!isRecord(client)) return []
|
||||||
|
const agentObj = client.agent
|
||||||
|
if (!isRecord(agentObj)) return []
|
||||||
|
const listFn = agentObj.list
|
||||||
|
if (typeof listFn !== "function") return []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await listFn.call(agentObj)
|
||||||
|
if (!isRecord(response)) return []
|
||||||
|
return parseRegisteredAgentSummaries(response.data)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCustomAgentMetadata(agentName: string, description: string): AgentPromptMetadata {
|
||||||
|
const shortDescription = truncateDescription(description).trim()
|
||||||
|
return {
|
||||||
|
category: "specialist",
|
||||||
|
cost: "CHEAP",
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
domain: `Custom agent: ${agentName}`,
|
||||||
|
trigger: shortDescription || "Use when this agent's description matches the task",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function buildAgent(
|
export function buildAgent(
|
||||||
source: AgentSource,
|
source: AgentSource,
|
||||||
model: string,
|
model: string,
|
||||||
@ -279,6 +349,10 @@ export async function createBuiltinAgents(
|
|||||||
|
|
||||||
const availableSkills: AvailableSkill[] = [...builtinAvailable, ...discoveredAvailable]
|
const availableSkills: AvailableSkill[] = [...builtinAvailable, ...discoveredAvailable]
|
||||||
|
|
||||||
|
const registeredAgents = await fetchRegisteredAgentsFromClient(client)
|
||||||
|
const builtinAgentNames = new Set(Object.keys(agentSources).map((n) => n.toLowerCase()))
|
||||||
|
const disabledAgentNames = new Set(disabledAgents.map((n) => n.toLowerCase()))
|
||||||
|
|
||||||
// Collect general agents first (for availableAgents), but don't add to result yet
|
// Collect general agents first (for availableAgents), but don't add to result yet
|
||||||
const pendingAgentConfigs: Map<string, AgentConfig> = new Map()
|
const pendingAgentConfigs: Map<string, AgentConfig> = new Map()
|
||||||
|
|
||||||
@ -335,14 +409,27 @@ export async function createBuiltinAgents(
|
|||||||
// Store for later - will be added after sisyphus and hephaestus
|
// Store for later - will be added after sisyphus and hephaestus
|
||||||
pendingAgentConfigs.set(name, config)
|
pendingAgentConfigs.set(name, config)
|
||||||
|
|
||||||
const metadata = agentMetadata[agentName]
|
const metadata = agentMetadata[agentName]
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
availableAgents.push({
|
availableAgents.push({
|
||||||
name: agentName,
|
name: agentName,
|
||||||
description: config.description ?? "",
|
description: config.description ?? "",
|
||||||
metadata,
|
metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const agent of registeredAgents) {
|
||||||
|
const lowerName = agent.name.toLowerCase()
|
||||||
|
if (builtinAgentNames.has(lowerName)) continue
|
||||||
|
if (disabledAgentNames.has(lowerName)) continue
|
||||||
|
if (availableAgents.some((a) => a.name.toLowerCase() === lowerName)) continue
|
||||||
|
|
||||||
|
availableAgents.push({
|
||||||
|
name: agent.name,
|
||||||
|
description: agent.description,
|
||||||
|
metadata: buildCustomAgentMetadata(agent.name, agent.description),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const sisyphusOverride = agentOverrides["sisyphus"]
|
const sisyphusOverride = agentOverrides["sisyphus"]
|
||||||
|
|||||||
@ -21,6 +21,9 @@ function createMockPluginInput(options: {
|
|||||||
prompt: async (input: unknown) => {
|
prompt: async (input: unknown) => {
|
||||||
promptCalls.push({ input })
|
promptCalls.push({ input })
|
||||||
},
|
},
|
||||||
|
promptAsync: async (input: unknown) => {
|
||||||
|
promptCalls.push({ input })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user