diff --git a/src/features/team-mode/tools/lifecycle.test.ts b/src/features/team-mode/tools/lifecycle.test.ts index e0ae123df..9eed262ab 100644 --- a/src/features/team-mode/tools/lifecycle.test.ts +++ b/src/features/team-mode/tools/lifecycle.test.ts @@ -284,6 +284,34 @@ describe("team lifecycle tools", () => { expect(hasRuntime(created.teamRunId)).toBe(false) }) + test("team_create denies a hard-reject caller even when spec has an explicit lead field", async () => { + // given + const teamCreateTool = createTeamCreateToolForTest() + const prometheusContext = { + ...createToolContext("lead-session"), + agent: "prometheus", + } + const inlineSpec = { + name: "alpha-team", + lead: { kind: "subagent_type", subagent_type: "sisyphus" }, + members: [{ kind: "category", name: "member-a", category: "quick", prompt: "Do the assigned work" }], + } + + // when + let errorMessage = "" + try { + await teamCreateTool.execute({ inline_spec: inlineSpec }, prometheusContext) + } catch (error) { + errorMessage = error instanceof Error ? error.message : String(error) + } + + // then + expect(errorMessage).toContain("team_create denied") + expect(errorMessage).toContain("prometheus") + expect(errorMessage).toContain("hard-reject") + expect(createTeamRunMock).not.toHaveBeenCalled() + }) + test("team_reject_shutdown records the rejection reason", async () => { // given const createTool = createTeamCreateToolForTest() diff --git a/src/features/team-mode/tools/lifecycle.ts b/src/features/team-mode/tools/lifecycle.ts index 5fd4f0787..fdda0129d 100644 --- a/src/features/team-mode/tools/lifecycle.ts +++ b/src/features/team-mode/tools/lifecycle.ts @@ -8,7 +8,9 @@ import { mergeCategories } from "../../../shared/merge-categories" import type { OpencodeClient } from "../../../tools/delegate-task/types" import type { BackgroundManager } from "../../background-agent/manager" import type { TmuxSessionManager } from "../../tmux-subagent/manager" +import { getAgentConfigKey } from "../../../shared/agent-display-names" import { resolveCallerTeamLead } from "../resolve-caller-team-lead" +import { AGENT_ELIGIBILITY_REGISTRY } from "../types" import { loadTeamSpec, normalizeTeamSpecInput } from "../team-registry/loader" import { validateSpec } from "../team-registry/validator" import { createTeamRun } from "../team-runtime/create" @@ -197,6 +199,13 @@ export function createTeamCreateTool( if (!leadSessionId) throw new Error("team_create requires leadSessionId or tool context sessionID") const projectRoot = typeof runtimeContext.directory === "string" ? runtimeContext.directory : process.cwd() const callerTeamLead = resolveCallerTeamLead(runtimeContext.agent) + if (callerTeamLead.displayName !== undefined) { + const callerAgentKey = getAgentConfigKey(callerTeamLead.displayName) + const callerRegistryEntry = AGENT_ELIGIBILITY_REGISTRY[callerAgentKey] + if (callerRegistryEntry?.verdict === "hard-reject") { + throw new Error(`team_create denied: caller '${callerAgentKey}' is a hard-reject agent and cannot create teams regardless of an explicit 'lead' in the spec. ${callerRegistryEntry.rejectionMessage ?? `Agent '${callerAgentKey}' is not eligible to lead a team.`}`) + } + } const defaultCategoryName = resolveDefaultInlineCategory(executorConfig?.userCategories) const spec = args.teamName ? await deps.loadTeamSpec(args.teamName, config, projectRoot, { callerTeamLead })