From ca8732f8feec296a7ae11ca671ff92e83b6fba04 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 17:57:49 +0900 Subject: [PATCH] fix(team-mode): reject incomplete hyperplan teams Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../team-mode/team-registry/validator.test.ts | 53 +++++++++++++++++++ .../team-mode/team-registry/validator.ts | 30 +++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/features/team-mode/team-registry/validator.test.ts b/src/features/team-mode/team-registry/validator.test.ts index b101ded2c..dce57dc52 100644 --- a/src/features/team-mode/team-registry/validator.test.ts +++ b/src/features/team-mode/team-registry/validator.test.ts @@ -26,6 +26,17 @@ function createCategoryMember(name: string): Member { } } +function createHyperplanMember(name: string, category: string): Member { + return { + kind: "category", + name, + category, + prompt: `perform the ${name} adversarial role`, + backendType: "in-process", + isActive: true, + } +} + function createBaseTeamSpec(): TeamSpec { return { version: 1, @@ -146,6 +157,48 @@ describe("team-registry validator", () => { expect(act).toThrow("Team 'validator-team' exceeds max 8 members.") }) + test("rejects hyperplan teams that omit required adversarial categories", () => { + // given + const teamSpec: TeamSpec = { + version: 1, + name: "hyperplan", + createdAt: 1, + leadAgentId: "architect", + members: [ + createHyperplanMember("researcher", "deep"), + createHyperplanMember("architect", "ultrabrain"), + ], + } + + // when + const act = () => validateSpec(teamSpec) + + // then + expect(act).toThrow("Hyperplan team must include category 'unspecified-low'.") + }) + + test("accepts hyperplan teams with required adversarial categories and optional deep", () => { + // given + const teamSpec: TeamSpec = { + version: 1, + name: "hyperplan", + createdAt: 1, + leadAgentId: "architect", + members: [ + createHyperplanMember("skeptic", "unspecified-low"), + createHyperplanMember("validator", "unspecified-high"), + createHyperplanMember("architect", "ultrabrain"), + createHyperplanMember("creative", "artistry"), + ], + } + + // when + const act = () => validateSpec(teamSpec) + + // then + expect(act).not.toThrow() + }) + test("rejects category prompts that collapse to empty text", () => { // given const member: Member = { diff --git a/src/features/team-mode/team-registry/validator.ts b/src/features/team-mode/team-registry/validator.ts index ceea0a1f0..ba9347afa 100644 --- a/src/features/team-mode/team-registry/validator.ts +++ b/src/features/team-mode/team-registry/validator.ts @@ -3,6 +3,12 @@ import { AGENT_ELIGIBILITY_REGISTRY } from "../types" import type { Member, TeamSpec } from "../types" const MAX_TEAM_MEMBERS = 8 +const HYPERPLAN_REQUIRED_CATEGORIES = [ + "unspecified-low", + "unspecified-high", + "ultrabrain", + "artistry", +] as const const UNKNOWN_SUBAGENT_MESSAGE = "Unknown subagent_type ''. Available ELIGIBLE agents: sisyphus, atlas, sisyphus-junior, hephaestus (if D-36 applied). Use delegate-task for read-only agents like oracle, librarian, explore, metis, momus, multimodal-looker." @@ -56,6 +62,30 @@ export function validateSpec(spec: TeamSpec): void { "leadAgentId", ) } + + validateHyperplanComposition(spec) +} + +function validateHyperplanComposition(spec: TeamSpec): void { + if (spec.name !== "hyperplan") { + return + } + + const categories = new Set( + spec.members + .filter((member) => member.kind === "category") + .map((member) => member.category), + ) + + for (const category of HYPERPLAN_REQUIRED_CATEGORIES) { + if (!categories.has(category)) { + throw new TeamSpecValidationError( + `Hyperplan team must include category '${category}'.`, + "HYPERPLAN_REQUIRED_CATEGORY_MISSING", + "members", + ) + } + } } export function validateMemberEligibility(member: Member): void {