feat(agent-teams): support category-based teammate spawning
This commit is contained in:
committed by
YeonGyu-Kim
parent
3f859828cc
commit
e984ce7493
@@ -1,4 +1,8 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { CategoriesConfig } from "../../config/schema"
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
import { resolveCategoryExecution, resolveParentContext } from "../delegate-task/executor"
|
||||
import type { DelegateTaskArgs, ToolContextWithMetadata } from "../delegate-task/types"
|
||||
import { clearInbox, ensureInbox, sendPlainInboxMessage } from "./inbox-store"
|
||||
import { assignNextColor, getTeamMember, removeTeammate, updateTeamConfig, upsertTeammate } from "./team-config-store"
|
||||
import type { TeamTeammateMember, TeamToolContext } from "./types"
|
||||
@@ -33,13 +37,24 @@ function resolveLaunchFailureMessage(status: string | undefined, error: string |
|
||||
return "teammate_launch_timeout"
|
||||
}
|
||||
|
||||
function buildLaunchPrompt(teamName: string, teammateName: string, userPrompt: string): string {
|
||||
return [
|
||||
function buildLaunchPrompt(
|
||||
teamName: string,
|
||||
teammateName: string,
|
||||
userPrompt: string,
|
||||
categoryPromptAppend?: string,
|
||||
): string {
|
||||
const sections = [
|
||||
`You are teammate "${teammateName}" in team "${teamName}".`,
|
||||
`When you need updates, call read_inbox with team_name="${teamName}" and agent_name="${teammateName}".`,
|
||||
"Initial assignment:",
|
||||
userPrompt,
|
||||
].join("\n\n")
|
||||
]
|
||||
|
||||
if (categoryPromptAppend) {
|
||||
sections.push("Category guidance:", categoryPromptAppend)
|
||||
}
|
||||
|
||||
return sections.join("\n\n")
|
||||
}
|
||||
|
||||
function buildDeliveryPrompt(teamName: string, teammateName: string, summary: string, content: string): string {
|
||||
@@ -55,14 +70,103 @@ export interface SpawnTeammateParams {
|
||||
teamName: string
|
||||
name: string
|
||||
prompt: string
|
||||
category?: string
|
||||
subagentType: string
|
||||
model?: string
|
||||
planModeRequired: boolean
|
||||
context: TeamToolContext
|
||||
manager: BackgroundManager
|
||||
categoryContext?: {
|
||||
client: PluginInput["client"]
|
||||
userCategories?: CategoriesConfig
|
||||
sisyphusJuniorModel?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface SpawnExecution {
|
||||
agentType: string
|
||||
teammateModel: string
|
||||
launchModel?: { providerID: string; modelID: string; variant?: string }
|
||||
categoryPromptAppend?: string
|
||||
}
|
||||
|
||||
async function getSystemDefaultModel(client: PluginInput["client"]): Promise<string | undefined> {
|
||||
try {
|
||||
const openCodeConfig = await client.config.get()
|
||||
return (openCodeConfig as { data?: { model?: string } })?.data?.model
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveSpawnExecution(params: SpawnTeammateParams): Promise<SpawnExecution> {
|
||||
if (!params.category) {
|
||||
const launchModel = parseModel(params.model)
|
||||
return {
|
||||
agentType: params.subagentType,
|
||||
teammateModel: params.model ?? "native",
|
||||
...(launchModel ? { launchModel } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.categoryContext?.client) {
|
||||
throw new Error("category_requires_client_context")
|
||||
}
|
||||
|
||||
const parentContext = resolveParentContext({
|
||||
sessionID: params.context.sessionID,
|
||||
messageID: params.context.messageID,
|
||||
agent: params.context.agent ?? "sisyphus",
|
||||
abort: new AbortController().signal,
|
||||
} as ToolContextWithMetadata)
|
||||
|
||||
const inheritedModel = parentContext.model
|
||||
? `${parentContext.model.providerID}/${parentContext.model.modelID}`
|
||||
: undefined
|
||||
|
||||
const systemDefaultModel = await getSystemDefaultModel(params.categoryContext.client)
|
||||
|
||||
const delegateArgs: DelegateTaskArgs = {
|
||||
description: `[team:${params.teamName}] ${params.name}`,
|
||||
prompt: params.prompt,
|
||||
category: params.category,
|
||||
subagent_type: "sisyphus-junior",
|
||||
run_in_background: true,
|
||||
load_skills: [],
|
||||
}
|
||||
|
||||
const resolution = await resolveCategoryExecution(
|
||||
delegateArgs,
|
||||
{
|
||||
manager: params.manager,
|
||||
client: params.categoryContext.client,
|
||||
directory: process.cwd(),
|
||||
userCategories: params.categoryContext.userCategories,
|
||||
sisyphusJuniorModel: params.categoryContext.sisyphusJuniorModel,
|
||||
},
|
||||
inheritedModel,
|
||||
systemDefaultModel,
|
||||
)
|
||||
|
||||
if (resolution.error) {
|
||||
throw new Error(resolution.error)
|
||||
}
|
||||
|
||||
if (!resolution.categoryModel) {
|
||||
throw new Error("category_model_not_resolved")
|
||||
}
|
||||
|
||||
return {
|
||||
agentType: resolution.agentToUse,
|
||||
teammateModel: `${resolution.categoryModel.providerID}/${resolution.categoryModel.modelID}`,
|
||||
launchModel: resolution.categoryModel,
|
||||
categoryPromptAppend: resolution.categoryPromptAppend,
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTeammateMember> {
|
||||
const execution = await resolveSpawnExecution(params)
|
||||
|
||||
let teammate: TeamTeammateMember | undefined
|
||||
let launchedTaskID: string | undefined
|
||||
|
||||
@@ -74,8 +178,9 @@ export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTe
|
||||
teammate = {
|
||||
agentId: `${params.name}@${params.teamName}`,
|
||||
name: params.name,
|
||||
agentType: params.subagentType,
|
||||
model: params.model ?? "native",
|
||||
agentType: execution.agentType,
|
||||
...(params.category ? { category: params.category } : {}),
|
||||
model: execution.teammateModel,
|
||||
prompt: params.prompt,
|
||||
color: assignNextColor(current),
|
||||
planModeRequired: params.planModeRequired,
|
||||
@@ -97,14 +202,14 @@ export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTe
|
||||
ensureInbox(params.teamName, params.name)
|
||||
sendPlainInboxMessage(params.teamName, "team-lead", params.name, params.prompt, "initial_prompt", teammate.color)
|
||||
|
||||
const resolvedModel = parseModel(params.model)
|
||||
const launched = await params.manager.launch({
|
||||
description: `[team:${params.teamName}] ${params.name}`,
|
||||
prompt: buildLaunchPrompt(params.teamName, params.name, params.prompt),
|
||||
agent: params.subagentType,
|
||||
prompt: buildLaunchPrompt(params.teamName, params.name, params.prompt, execution.categoryPromptAppend),
|
||||
agent: execution.agentType,
|
||||
parentSessionID: params.context.sessionID,
|
||||
parentMessageID: params.context.messageID,
|
||||
...(resolvedModel ? { model: resolvedModel } : {}),
|
||||
...(execution.launchModel ? { model: execution.launchModel } : {}),
|
||||
...(params.category ? { category: params.category } : {}),
|
||||
parentAgent: params.context.agent,
|
||||
})
|
||||
launchedTaskID = launched.id
|
||||
|
||||
Reference in New Issue
Block a user