feat(agent-teams): add team, message, and task Zod schemas
- TeamConfigSchema with lead/teammate members - TeamMemberSchema and TeamTeammateMemberSchema - InboxMessageSchema with 5 message types - SendMessageInputSchema as discriminated union - Import TaskObjectSchema from tools/task/types.ts - 39 comprehensive tests covering all schemas Task 3/25 complete
This commit is contained in:
@@ -1,10 +1,198 @@
|
||||
/// <reference types="bun-types" />
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { TeamTeammateMemberSchema } from "./types"
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
TeamConfigSchema,
|
||||
TeamMemberSchema,
|
||||
TeamTeammateMemberSchema,
|
||||
MessageTypeSchema,
|
||||
InboxMessageSchema,
|
||||
TeamTaskSchema,
|
||||
TeamCreateInputSchema,
|
||||
TeamDeleteInputSchema,
|
||||
SendMessageInputSchema,
|
||||
ReadInboxInputSchema,
|
||||
ReadConfigInputSchema,
|
||||
TeamSpawnInputSchema,
|
||||
ForceKillTeammateInputSchema,
|
||||
ProcessShutdownApprovedInputSchema,
|
||||
} from "./types"
|
||||
|
||||
describe("agent-teams types", () => {
|
||||
test("rejects reserved agentType for teammate schema", () => {
|
||||
//#given
|
||||
describe("TeamConfigSchema", () => {
|
||||
it("validates a complete team config", () => {
|
||||
// given
|
||||
const validConfig = {
|
||||
name: "my-team",
|
||||
description: "A test team",
|
||||
createdAt: "2026-02-11T10:00:00Z",
|
||||
leadAgentId: "agent-123",
|
||||
leadSessionId: "ses-456",
|
||||
members: [
|
||||
{
|
||||
agentId: "agent-123",
|
||||
name: "Lead Agent",
|
||||
agentType: "lead",
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
agentId: "agent-789",
|
||||
name: "Worker 1",
|
||||
agentType: "teammate",
|
||||
color: "green",
|
||||
category: "quick",
|
||||
model: "claude-sonnet-4-5",
|
||||
prompt: "You are a helpful assistant",
|
||||
planModeRequired: false,
|
||||
joinedAt: "2026-02-11T10:05:00Z",
|
||||
cwd: "/tmp",
|
||||
subscriptions: ["task-updates"],
|
||||
backendType: "native" as const,
|
||||
isActive: true,
|
||||
sessionID: "ses-789",
|
||||
backgroundTaskID: "task-123",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamConfigSchema.safeParse(validConfig)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid team config", () => {
|
||||
// given
|
||||
const invalidConfig = {
|
||||
name: "",
|
||||
description: "A test team",
|
||||
createdAt: "invalid-date",
|
||||
leadAgentId: "",
|
||||
leadSessionId: "ses-456",
|
||||
members: [],
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamConfigSchema.safeParse(invalidConfig)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamMemberSchema", () => {
|
||||
it("validates a lead member", () => {
|
||||
// given
|
||||
const leadMember = {
|
||||
agentId: "agent-123",
|
||||
name: "Lead Agent",
|
||||
agentType: "lead",
|
||||
color: "blue",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamMemberSchema.safeParse(leadMember)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid member", () => {
|
||||
// given
|
||||
const invalidMember = {
|
||||
agentId: "",
|
||||
name: "",
|
||||
agentType: "invalid",
|
||||
color: "invalid",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamMemberSchema.safeParse(invalidMember)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamTeammateMemberSchema", () => {
|
||||
it("validates a complete teammate member", () => {
|
||||
// given
|
||||
const teammateMember = {
|
||||
agentId: "agent-789",
|
||||
name: "Worker 1",
|
||||
agentType: "teammate",
|
||||
color: "green",
|
||||
category: "quick",
|
||||
model: "claude-sonnet-4-5",
|
||||
prompt: "You are a helpful assistant",
|
||||
planModeRequired: false,
|
||||
joinedAt: "2026-02-11T10:05:00Z",
|
||||
cwd: "/tmp",
|
||||
subscriptions: ["task-updates"],
|
||||
backendType: "native" as const,
|
||||
isActive: true,
|
||||
sessionID: "ses-789",
|
||||
backgroundTaskID: "task-123",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamTeammateMemberSchema.safeParse(teammateMember)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates teammate member with optional fields missing", () => {
|
||||
// given
|
||||
const minimalTeammate = {
|
||||
agentId: "agent-789",
|
||||
name: "Worker 1",
|
||||
agentType: "teammate",
|
||||
color: "green",
|
||||
category: "quick",
|
||||
model: "claude-sonnet-4-5",
|
||||
prompt: "You are a helpful assistant",
|
||||
planModeRequired: false,
|
||||
joinedAt: "2026-02-11T10:05:00Z",
|
||||
cwd: "/tmp",
|
||||
subscriptions: [],
|
||||
backendType: "native" as const,
|
||||
isActive: true,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamTeammateMemberSchema.safeParse(minimalTeammate)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid teammate member", () => {
|
||||
// given
|
||||
const invalidTeammate = {
|
||||
agentId: "",
|
||||
name: "Worker 1",
|
||||
agentType: "teammate",
|
||||
color: "green",
|
||||
category: "quick",
|
||||
model: "claude-sonnet-4-5",
|
||||
prompt: "You are a helpful assistant",
|
||||
planModeRequired: false,
|
||||
joinedAt: "invalid-date",
|
||||
cwd: "/tmp",
|
||||
subscriptions: [],
|
||||
backendType: "invalid" as const,
|
||||
isActive: true,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamTeammateMemberSchema.safeParse(invalidTeammate)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects reserved agentType for teammate schema", () => {
|
||||
// given
|
||||
const invalidTeammate = {
|
||||
agentId: "worker@team",
|
||||
name: "worker",
|
||||
@@ -14,17 +202,520 @@ describe("agent-teams types", () => {
|
||||
prompt: "do work",
|
||||
color: "blue",
|
||||
planModeRequired: false,
|
||||
joinedAt: Date.now(),
|
||||
joinedAt: "2026-02-11T10:05:00Z",
|
||||
cwd: "/tmp",
|
||||
subscriptions: [],
|
||||
backendType: "native",
|
||||
isActive: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
// when
|
||||
const result = TeamTeammateMemberSchema.safeParse(invalidTeammate)
|
||||
|
||||
//#then
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("MessageTypeSchema", () => {
|
||||
it("validates all 5 message types", () => {
|
||||
// given
|
||||
const types = ["message", "broadcast", "shutdown_request", "shutdown_response", "plan_approval_response"]
|
||||
|
||||
// when & then
|
||||
types.forEach(type => {
|
||||
const result = MessageTypeSchema.safeParse(type)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data).toBe(type)
|
||||
})
|
||||
})
|
||||
|
||||
it("rejects invalid message type", () => {
|
||||
// given
|
||||
const invalidType = "invalid_type"
|
||||
|
||||
// when
|
||||
const result = MessageTypeSchema.safeParse(invalidType)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("InboxMessageSchema", () => {
|
||||
it("validates a complete inbox message", () => {
|
||||
// given
|
||||
const message = {
|
||||
id: "msg-123",
|
||||
type: "message" as const,
|
||||
sender: "agent-123",
|
||||
recipient: "agent-456",
|
||||
content: "Hello world",
|
||||
summary: "Greeting",
|
||||
timestamp: "2026-02-11T10:00:00Z",
|
||||
read: false,
|
||||
requestId: "req-123",
|
||||
approve: true,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = InboxMessageSchema.safeParse(message)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates message with optional fields missing", () => {
|
||||
// given
|
||||
const minimalMessage = {
|
||||
id: "msg-123",
|
||||
type: "broadcast" as const,
|
||||
sender: "agent-123",
|
||||
recipient: "agent-456",
|
||||
content: "Hello world",
|
||||
summary: "Greeting",
|
||||
timestamp: "2026-02-11T10:00:00Z",
|
||||
read: false,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = InboxMessageSchema.safeParse(minimalMessage)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid inbox message", () => {
|
||||
// given
|
||||
const invalidMessage = {
|
||||
id: "",
|
||||
type: "invalid" as const,
|
||||
sender: "",
|
||||
recipient: "",
|
||||
content: "",
|
||||
summary: "",
|
||||
timestamp: "invalid-date",
|
||||
read: "not-boolean",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = InboxMessageSchema.safeParse(invalidMessage)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamTaskSchema", () => {
|
||||
it("validates a task object", () => {
|
||||
// given
|
||||
const task = {
|
||||
id: "T-12345678-1234-1234-1234-123456789012",
|
||||
subject: "Implement feature",
|
||||
description: "Add new functionality",
|
||||
status: "pending" as const,
|
||||
activeForm: "Implementing feature",
|
||||
blocks: [],
|
||||
blockedBy: [],
|
||||
owner: "agent-123",
|
||||
metadata: { priority: "high" },
|
||||
repoURL: "https://github.com/user/repo",
|
||||
parentID: "T-parent",
|
||||
threadID: "thread-123",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamTaskSchema.safeParse(task)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid task", () => {
|
||||
// given
|
||||
const invalidTask = {
|
||||
id: "invalid-id",
|
||||
subject: "",
|
||||
description: "Add new functionality",
|
||||
status: "invalid" as const,
|
||||
activeForm: "Implementing feature",
|
||||
blocks: [],
|
||||
blockedBy: [],
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamTaskSchema.safeParse(invalidTask)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamCreateInputSchema", () => {
|
||||
it("validates create input with description", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
description: "A test team",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamCreateInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates create input without description", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamCreateInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid create input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "invalid team name with spaces and special chars!",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamCreateInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamDeleteInputSchema", () => {
|
||||
it("validates delete input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamDeleteInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid delete input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamDeleteInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("SendMessageInputSchema", () => {
|
||||
it("validates message type input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "message" as const,
|
||||
recipient: "agent-456",
|
||||
content: "Hello world",
|
||||
summary: "Greeting",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates broadcast type input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "broadcast" as const,
|
||||
content: "Team announcement",
|
||||
summary: "Announcement",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates shutdown_request type input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "shutdown_request" as const,
|
||||
recipient: "agent-456",
|
||||
content: "Please shutdown",
|
||||
summary: "Shutdown request",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates shutdown_response type input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "shutdown_response" as const,
|
||||
request_id: "req-123",
|
||||
approve: true,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates plan_approval_response type input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "plan_approval_response" as const,
|
||||
request_id: "req-456",
|
||||
approve: false,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects message type without recipient", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "message" as const,
|
||||
content: "Hello world",
|
||||
summary: "Greeting",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects shutdown_response without request_id", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
type: "shutdown_response" as const,
|
||||
approve: true,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects invalid team_name", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "invalid team name",
|
||||
type: "broadcast" as const,
|
||||
content: "Hello",
|
||||
summary: "Greeting",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = SendMessageInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ReadInboxInputSchema", () => {
|
||||
it("validates read inbox input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
agent_name: "worker-1",
|
||||
unread_only: true,
|
||||
mark_as_read: false,
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ReadInboxInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates minimal read inbox input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
agent_name: "worker-1",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ReadInboxInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid read inbox input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "",
|
||||
agent_name: "",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ReadInboxInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ReadConfigInputSchema", () => {
|
||||
it("validates read config input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ReadConfigInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid read config input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ReadConfigInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("TeamSpawnInputSchema", () => {
|
||||
it("validates spawn input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
name: "worker-1",
|
||||
category: "quick",
|
||||
prompt: "You are a helpful assistant",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamSpawnInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid spawn input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "invalid team",
|
||||
name: "",
|
||||
category: "quick",
|
||||
prompt: "You are a helpful assistant",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = TeamSpawnInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ForceKillTeammateInputSchema", () => {
|
||||
it("validates force kill input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
teammate_name: "worker-1",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ForceKillTeammateInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid force kill input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "",
|
||||
teammate_name: "",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ForceKillTeammateInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProcessShutdownApprovedInputSchema", () => {
|
||||
it("validates shutdown approved input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "my-team",
|
||||
teammate_name: "worker-1",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ProcessShutdownApprovedInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects invalid shutdown approved input", () => {
|
||||
// given
|
||||
const input = {
|
||||
team_name: "",
|
||||
teammate_name: "",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = ProcessShutdownApprovedInputSchema.safeParse(input)
|
||||
|
||||
// then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,82 +1,52 @@
|
||||
import { z } from "zod"
|
||||
import { TaskObjectSchema } from "../task/types"
|
||||
|
||||
export const TEAM_COLOR_PALETTE = [
|
||||
"blue",
|
||||
"green",
|
||||
"yellow",
|
||||
"purple",
|
||||
"orange",
|
||||
"pink",
|
||||
"cyan",
|
||||
"red",
|
||||
] as const
|
||||
// Team member schemas
|
||||
export const TeamMemberSchema = z.object({
|
||||
agentId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
agentType: z.enum(["lead", "teammate"]),
|
||||
color: z.string().min(1),
|
||||
})
|
||||
|
||||
export const TeamTaskStatusSchema = z.enum(["pending", "in_progress", "completed", "deleted"])
|
||||
export type TeamTaskStatus = z.infer<typeof TeamTaskStatusSchema>
|
||||
export type TeamMember = z.infer<typeof TeamMemberSchema>
|
||||
|
||||
export const TeamLeadMemberSchema = z.object({
|
||||
agentId: z.string(),
|
||||
name: z.literal("team-lead"),
|
||||
agentType: z.literal("team-lead"),
|
||||
model: z.string(),
|
||||
joinedAt: z.number(),
|
||||
cwd: z.string(),
|
||||
subscriptions: z.array(z.unknown()).default([]),
|
||||
}).strict()
|
||||
|
||||
export const TeamTeammateMemberSchema = z.object({
|
||||
agentId: z.string(),
|
||||
name: z.string(),
|
||||
agentType: z.string().refine((value) => value !== "team-lead", {
|
||||
message: "agent_type_reserved",
|
||||
}),
|
||||
category: z.string(),
|
||||
model: z.string(),
|
||||
prompt: z.string(),
|
||||
color: z.string(),
|
||||
planModeRequired: z.boolean().default(false),
|
||||
joinedAt: z.number(),
|
||||
cwd: z.string(),
|
||||
subscriptions: z.array(z.unknown()).default([]),
|
||||
backendType: z.literal("native").default("native"),
|
||||
isActive: z.boolean().default(false),
|
||||
export const TeamTeammateMemberSchema = TeamMemberSchema.extend({
|
||||
category: z.string().min(1),
|
||||
model: z.string().min(1),
|
||||
prompt: z.string().min(1),
|
||||
planModeRequired: z.boolean(),
|
||||
joinedAt: z.string().datetime(),
|
||||
cwd: z.string().min(1),
|
||||
subscriptions: z.array(z.string()),
|
||||
backendType: z.literal("native"),
|
||||
isActive: z.boolean(),
|
||||
sessionID: z.string().optional(),
|
||||
backgroundTaskID: z.string().optional(),
|
||||
}).strict()
|
||||
}).refine(
|
||||
(data) => data.agentType === "teammate",
|
||||
"TeamTeammateMemberSchema requires agentType to be 'teammate'"
|
||||
).refine(
|
||||
(data) => data.agentType !== "team-lead",
|
||||
"agentType 'team-lead' is reserved and not allowed"
|
||||
)
|
||||
|
||||
export const TeamMemberSchema = z.union([TeamLeadMemberSchema, TeamTeammateMemberSchema])
|
||||
export type TeamTeammateMember = z.infer<typeof TeamTeammateMemberSchema>
|
||||
|
||||
// Team config schema
|
||||
export const TeamConfigSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().default(""),
|
||||
createdAt: z.number(),
|
||||
leadAgentId: z.string(),
|
||||
leadSessionId: z.string(),
|
||||
members: z.array(TeamMemberSchema),
|
||||
}).strict()
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
createdAt: z.string().datetime(),
|
||||
leadAgentId: z.string().min(1),
|
||||
leadSessionId: z.string().min(1),
|
||||
members: z.array(z.union([TeamMemberSchema, TeamTeammateMemberSchema])),
|
||||
})
|
||||
|
||||
export const TeamInboxMessageSchema = z.object({
|
||||
from: z.string(),
|
||||
text: z.string(),
|
||||
timestamp: z.string(),
|
||||
read: z.boolean().default(false),
|
||||
summary: z.string().optional(),
|
||||
color: z.string().optional(),
|
||||
}).strict()
|
||||
export type TeamConfig = z.infer<typeof TeamConfigSchema>
|
||||
|
||||
export const TeamTaskSchema = z.object({
|
||||
id: z.string(),
|
||||
subject: z.string(),
|
||||
description: z.string(),
|
||||
activeForm: z.string().optional(),
|
||||
status: TeamTaskStatusSchema,
|
||||
blocks: z.array(z.string()).default([]),
|
||||
blockedBy: z.array(z.string()).default([]),
|
||||
owner: z.string().optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
}).strict()
|
||||
|
||||
export const TeamSendMessageTypeSchema = z.enum([
|
||||
// Message schemas
|
||||
export const MessageTypeSchema = z.enum([
|
||||
"message",
|
||||
"broadcast",
|
||||
"shutdown_request",
|
||||
@@ -84,118 +54,113 @@ export const TeamSendMessageTypeSchema = z.enum([
|
||||
"plan_approval_response",
|
||||
])
|
||||
|
||||
export type TeamLeadMember = z.infer<typeof TeamLeadMemberSchema>
|
||||
export type TeamTeammateMember = z.infer<typeof TeamTeammateMemberSchema>
|
||||
export type TeamMember = z.infer<typeof TeamMemberSchema>
|
||||
export type TeamConfig = z.infer<typeof TeamConfigSchema>
|
||||
export type TeamInboxMessage = z.infer<typeof TeamInboxMessageSchema>
|
||||
export type TeamTask = z.infer<typeof TeamTaskSchema>
|
||||
export type TeamSendMessageType = z.infer<typeof TeamSendMessageTypeSchema>
|
||||
export type MessageType = z.infer<typeof MessageTypeSchema>
|
||||
|
||||
export const InboxMessageSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
type: MessageTypeSchema,
|
||||
sender: z.string().min(1),
|
||||
recipient: z.string().min(1),
|
||||
content: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
timestamp: z.string().datetime(),
|
||||
read: z.boolean(),
|
||||
requestId: z.string().optional(),
|
||||
approve: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export type InboxMessage = z.infer<typeof InboxMessageSchema>
|
||||
|
||||
// Task schema (reuse from task/types.ts)
|
||||
export const TeamTaskSchema = TaskObjectSchema
|
||||
|
||||
export type TeamTask = z.infer<typeof TeamTaskSchema>
|
||||
|
||||
// Input schemas for tools
|
||||
export const TeamCreateInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
|
||||
export type TeamCreateInput = z.infer<typeof TeamCreateInputSchema>
|
||||
|
||||
export const TeamDeleteInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
})
|
||||
|
||||
export const TeamReadConfigInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
})
|
||||
export type TeamDeleteInput = z.infer<typeof TeamDeleteInputSchema>
|
||||
|
||||
export const TeamSpawnInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
name: z.string(),
|
||||
prompt: z.string(),
|
||||
category: z.string(),
|
||||
subagent_type: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
plan_mode_required: z.boolean().optional(),
|
||||
})
|
||||
|
||||
function normalizeTeamRecipient(recipient: string): string {
|
||||
const trimmed = recipient.trim()
|
||||
const atIndex = trimmed.indexOf("@")
|
||||
if (atIndex <= 0) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
return trimmed.slice(0, atIndex)
|
||||
}
|
||||
|
||||
export const TeamSendMessageInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
type: TeamSendMessageTypeSchema,
|
||||
recipient: z.string().optional().transform((value) => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return normalizeTeamRecipient(value)
|
||||
export const SendMessageInputSchema = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
type: z.literal("message"),
|
||||
recipient: z.string().min(1),
|
||||
content: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
}),
|
||||
content: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
request_id: z.string().optional(),
|
||||
approve: z.boolean().optional(),
|
||||
sender: z.string().optional(),
|
||||
})
|
||||
z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
type: z.literal("broadcast"),
|
||||
content: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
type: z.literal("shutdown_request"),
|
||||
recipient: z.string().min(1),
|
||||
content: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
type: z.literal("shutdown_response"),
|
||||
request_id: z.string().min(1),
|
||||
approve: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
type: z.literal("plan_approval_response"),
|
||||
request_id: z.string().min(1),
|
||||
approve: z.boolean(),
|
||||
}),
|
||||
])
|
||||
|
||||
export const TeamReadInboxInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
agent_name: z.string(),
|
||||
export type SendMessageInput = z.infer<typeof SendMessageInputSchema>
|
||||
|
||||
export const ReadInboxInputSchema = z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
agent_name: z.string().min(1),
|
||||
unread_only: z.boolean().optional(),
|
||||
mark_as_read: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const TeamTaskCreateInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
subject: z.string(),
|
||||
description: z.string(),
|
||||
active_form: z.string().optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
export type ReadInboxInput = z.infer<typeof ReadInboxInputSchema>
|
||||
|
||||
export const ReadConfigInputSchema = z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
})
|
||||
|
||||
export const TeamTaskUpdateInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
task_id: z.string(),
|
||||
status: TeamTaskStatusSchema.optional(),
|
||||
owner: z.string().optional(),
|
||||
subject: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
active_form: z.string().optional(),
|
||||
add_blocks: z.array(z.string()).optional(),
|
||||
add_blocked_by: z.array(z.string()).optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
export type ReadConfigInput = z.infer<typeof ReadConfigInputSchema>
|
||||
|
||||
export const TeamSpawnInputSchema = z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
name: z.string().min(1),
|
||||
category: z.string().min(1),
|
||||
prompt: z.string().min(1),
|
||||
})
|
||||
|
||||
export const TeamTaskListInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
export type TeamSpawnInput = z.infer<typeof TeamSpawnInputSchema>
|
||||
|
||||
export const ForceKillTeammateInputSchema = z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
teammate_name: z.string().min(1),
|
||||
})
|
||||
|
||||
export const TeamTaskGetInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
task_id: z.string(),
|
||||
export type ForceKillTeammateInput = z.infer<typeof ForceKillTeammateInputSchema>
|
||||
|
||||
export const ProcessShutdownApprovedInputSchema = z.object({
|
||||
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
|
||||
teammate_name: z.string().min(1),
|
||||
})
|
||||
|
||||
export const TeamForceKillInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
agent_name: z.string(),
|
||||
})
|
||||
|
||||
export const TeamProcessShutdownInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
agent_name: z.string(),
|
||||
})
|
||||
|
||||
export interface TeamToolContext {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
abort: AbortSignal
|
||||
agent?: string
|
||||
}
|
||||
|
||||
export function isTeammateMember(member: TeamMember): member is TeamTeammateMember {
|
||||
return member.agentType !== "team-lead"
|
||||
}
|
||||
export type ProcessShutdownApprovedInput = z.infer<typeof ProcessShutdownApprovedInputSchema>
|
||||
|
||||
Reference in New Issue
Block a user