diff --git a/src/tools/agent-teams/name-validation.test.ts b/src/tools/agent-teams/name-validation.test.ts
index 9dd0b124..9e2c70ca 100644
--- a/src/tools/agent-teams/name-validation.test.ts
+++ b/src/tools/agent-teams/name-validation.test.ts
@@ -1,6 +1,11 @@
///
import { describe, expect, test } from "bun:test"
-import { validateAgentName, validateTeamName } from "./name-validation"
+import {
+ validateAgentName,
+ validateAgentNameOrLead,
+ validateTaskId,
+ validateTeamName,
+} from "./name-validation"
describe("agent-teams name validation", () => {
test("accepts valid team names", () => {
@@ -55,4 +60,19 @@ describe("agent-teams name validation", () => {
expect(validResult).toBeNull()
expect(invalidResult).toBe("agent_name_invalid")
})
+
+ test("allows team-lead for inbox-compatible validation", () => {
+ //#then
+ expect(validateAgentNameOrLead("team-lead")).toBeNull()
+ expect(validateAgentNameOrLead("worker_1")).toBeNull()
+ expect(validateAgentNameOrLead("worker one")).toBe("agent_name_invalid")
+ })
+
+ test("validates task ids", () => {
+ //#then
+ expect(validateTaskId("T-123")).toBeNull()
+ expect(validateTaskId("")).toBe("task_id_required")
+ expect(validateTaskId("../../etc/passwd")).toBe("task_id_invalid")
+ expect(validateTaskId("a".repeat(129))).toBe("task_id_too_long")
+ })
})
diff --git a/src/tools/agent-teams/name-validation.ts b/src/tools/agent-teams/name-validation.ts
index 10d868fa..dcf3fcf1 100644
--- a/src/tools/agent-teams/name-validation.ts
+++ b/src/tools/agent-teams/name-validation.ts
@@ -1,5 +1,7 @@
const VALID_NAME_RE = /^[A-Za-z0-9_-]+$/
const MAX_NAME_LENGTH = 64
+const VALID_TASK_ID_RE = /^[A-Za-z0-9_-]+$/
+const MAX_TASK_ID_LENGTH = 128
function validateName(value: string, label: "team" | "agent"): string | null {
if (!value || !value.trim()) {
@@ -27,3 +29,26 @@ export function validateAgentName(agentName: string): string | null {
}
return validateName(agentName, "agent")
}
+
+export function validateAgentNameOrLead(agentName: string): string | null {
+ if (agentName === "team-lead") {
+ return null
+ }
+ return validateName(agentName, "agent")
+}
+
+export function validateTaskId(taskId: string): string | null {
+ if (!taskId || !taskId.trim()) {
+ return "task_id_required"
+ }
+
+ if (!VALID_TASK_ID_RE.test(taskId)) {
+ return "task_id_invalid"
+ }
+
+ if (taskId.length > MAX_TASK_ID_LENGTH) {
+ return "task_id_too_long"
+ }
+
+ return null
+}
diff --git a/src/tools/agent-teams/types.test.ts b/src/tools/agent-teams/types.test.ts
new file mode 100644
index 00000000..85291866
--- /dev/null
+++ b/src/tools/agent-teams/types.test.ts
@@ -0,0 +1,29 @@
+///
+import { describe, expect, test } from "bun:test"
+import { TeamTeammateMemberSchema } from "./types"
+
+describe("agent-teams types", () => {
+ test("rejects reserved agentType for teammate schema", () => {
+ //#given
+ const invalidTeammate = {
+ agentId: "worker@team",
+ name: "worker",
+ agentType: "team-lead",
+ model: "native",
+ prompt: "do work",
+ color: "blue",
+ planModeRequired: false,
+ joinedAt: Date.now(),
+ cwd: "/tmp",
+ subscriptions: [],
+ backendType: "native",
+ isActive: false,
+ }
+
+ //#when
+ const result = TeamTeammateMemberSchema.safeParse(invalidTeammate)
+
+ //#then
+ expect(result.success).toBe(false)
+ })
+})
diff --git a/src/tools/agent-teams/types.ts b/src/tools/agent-teams/types.ts
index cfa76d76..3883238c 100644
--- a/src/tools/agent-teams/types.ts
+++ b/src/tools/agent-teams/types.ts
@@ -27,7 +27,9 @@ export const TeamLeadMemberSchema = z.object({
export const TeamTeammateMemberSchema = z.object({
agentId: z.string(),
name: z.string(),
- agentType: z.string(),
+ agentType: z.string().refine((value) => value !== "team-lead", {
+ message: "agent_type_reserved",
+ }),
model: z.string(),
prompt: z.string(),
color: z.string(),