test(agent-teams): add functional and utility coverage
This commit is contained in:
parent
766794e0f5
commit
db08cc22cc
58
src/tools/agent-teams/name-validation.test.ts
Normal file
58
src/tools/agent-teams/name-validation.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { validateAgentName, validateTeamName } from "./name-validation"
|
||||||
|
|
||||||
|
describe("agent-teams name validation", () => {
|
||||||
|
test("accepts valid team names", () => {
|
||||||
|
//#given
|
||||||
|
const validNames = ["team_1", "alpha-team", "A1"]
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = validNames.map(validateTeamName)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toEqual([null, null, null])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rejects invalid and empty team names", () => {
|
||||||
|
//#given
|
||||||
|
const blank = ""
|
||||||
|
const invalid = "team space"
|
||||||
|
const tooLong = "a".repeat(65)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const blankResult = validateTeamName(blank)
|
||||||
|
const invalidResult = validateTeamName(invalid)
|
||||||
|
const tooLongResult = validateTeamName(tooLong)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(blankResult).toBe("team_name_required")
|
||||||
|
expect(invalidResult).toBe("team_name_invalid")
|
||||||
|
expect(tooLongResult).toBe("team_name_too_long")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rejects reserved teammate name", () => {
|
||||||
|
//#given
|
||||||
|
const reservedName = "team-lead"
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = validateAgentName(reservedName)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("agent_name_reserved")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("validates regular agent names", () => {
|
||||||
|
//#given
|
||||||
|
const valid = "worker_1"
|
||||||
|
const invalid = "worker one"
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const validResult = validateAgentName(valid)
|
||||||
|
const invalidResult = validateAgentName(invalid)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(validResult).toBeNull()
|
||||||
|
expect(invalidResult).toBe("agent_name_invalid")
|
||||||
|
})
|
||||||
|
})
|
||||||
80
src/tools/agent-teams/paths.test.ts
Normal file
80
src/tools/agent-teams/paths.test.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||||
|
import { mkdtempSync, rmSync } from "node:fs"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import {
|
||||||
|
getAgentTeamsRootDir,
|
||||||
|
getTeamConfigPath,
|
||||||
|
getTeamDir,
|
||||||
|
getTeamInboxDir,
|
||||||
|
getTeamInboxPath,
|
||||||
|
getTeamTaskDir,
|
||||||
|
getTeamTaskPath,
|
||||||
|
getTeamsRootDir,
|
||||||
|
getTeamTasksRootDir,
|
||||||
|
} from "./paths"
|
||||||
|
|
||||||
|
describe("agent-teams paths", () => {
|
||||||
|
let originalCwd: string
|
||||||
|
let tempProjectDir: string
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalCwd = process.cwd()
|
||||||
|
tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-paths-"))
|
||||||
|
process.chdir(tempProjectDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
rmSync(tempProjectDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses project-local .sisyphus directory as storage root", () => {
|
||||||
|
//#given
|
||||||
|
const expectedRoot = join(tempProjectDir, ".sisyphus", "agent-teams")
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const root = getAgentTeamsRootDir()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(root).toBe(expectedRoot)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("builds expected teams and tasks root directories", () => {
|
||||||
|
//#given
|
||||||
|
const expectedRoot = join(tempProjectDir, ".sisyphus", "agent-teams")
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const teamsRoot = getTeamsRootDir()
|
||||||
|
const tasksRoot = getTeamTasksRootDir()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(teamsRoot).toBe(join(expectedRoot, "teams"))
|
||||||
|
expect(tasksRoot).toBe(join(expectedRoot, "tasks"))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("builds team-scoped config, inbox, and task file paths", () => {
|
||||||
|
//#given
|
||||||
|
const teamName = "alpha_team"
|
||||||
|
const agentName = "worker_1"
|
||||||
|
const taskId = "T-123"
|
||||||
|
const expectedTeamDir = join(getTeamsRootDir(), teamName)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const teamDir = getTeamDir(teamName)
|
||||||
|
const configPath = getTeamConfigPath(teamName)
|
||||||
|
const inboxDir = getTeamInboxDir(teamName)
|
||||||
|
const inboxPath = getTeamInboxPath(teamName, agentName)
|
||||||
|
const taskDir = getTeamTaskDir(teamName)
|
||||||
|
const taskPath = getTeamTaskPath(teamName, taskId)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(teamDir).toBe(expectedTeamDir)
|
||||||
|
expect(configPath).toBe(join(expectedTeamDir, "config.json"))
|
||||||
|
expect(inboxDir).toBe(join(expectedTeamDir, "inboxes"))
|
||||||
|
expect(inboxPath).toBe(join(expectedTeamDir, "inboxes", `${agentName}.json`))
|
||||||
|
expect(taskDir).toBe(join(getTeamTasksRootDir(), teamName))
|
||||||
|
expect(taskPath).toBe(join(getTeamTasksRootDir(), teamName, `${taskId}.json`))
|
||||||
|
})
|
||||||
|
})
|
||||||
94
src/tools/agent-teams/team-task-dependency.test.ts
Normal file
94
src/tools/agent-teams/team-task-dependency.test.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import {
|
||||||
|
addPendingEdge,
|
||||||
|
createPendingEdgeMap,
|
||||||
|
ensureDependenciesCompleted,
|
||||||
|
ensureForwardStatusTransition,
|
||||||
|
wouldCreateCycle,
|
||||||
|
} from "./team-task-dependency"
|
||||||
|
import type { TeamTask, TeamTaskStatus } from "./types"
|
||||||
|
|
||||||
|
function createTask(id: string, status: TeamTaskStatus, blockedBy: string[] = []): TeamTask {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
subject: `Task ${id}`,
|
||||||
|
description: `Description ${id}`,
|
||||||
|
status,
|
||||||
|
blocks: [],
|
||||||
|
blockedBy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("agent-teams task dependency utilities", () => {
|
||||||
|
test("detects cycle from existing blockedBy chain", () => {
|
||||||
|
//#given
|
||||||
|
const tasks = new Map<string, TeamTask>([
|
||||||
|
["A", createTask("A", "pending", ["B"])],
|
||||||
|
["B", createTask("B", "pending")],
|
||||||
|
])
|
||||||
|
const pending = createPendingEdgeMap()
|
||||||
|
const readTask = (id: string) => tasks.get(id) ?? null
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const hasCycle = wouldCreateCycle("B", "A", pending, readTask)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(hasCycle).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("detects cycle from pending edge map", () => {
|
||||||
|
//#given
|
||||||
|
const tasks = new Map<string, TeamTask>([["A", createTask("A", "pending")]])
|
||||||
|
const pending = createPendingEdgeMap()
|
||||||
|
addPendingEdge(pending, "A", "B")
|
||||||
|
const readTask = (id: string) => tasks.get(id) ?? null
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const hasCycle = wouldCreateCycle("B", "A", pending, readTask)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(hasCycle).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns false when dependency graph has no cycle", () => {
|
||||||
|
//#given
|
||||||
|
const tasks = new Map<string, TeamTask>([
|
||||||
|
["A", createTask("A", "pending")],
|
||||||
|
["B", createTask("B", "pending", ["A"])],
|
||||||
|
])
|
||||||
|
const pending = createPendingEdgeMap()
|
||||||
|
const readTask = (id: string) => tasks.get(id) ?? null
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const hasCycle = wouldCreateCycle("C", "B", pending, readTask)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(hasCycle).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("allows forward status transitions and blocks backward transitions", () => {
|
||||||
|
//#then
|
||||||
|
expect(() => ensureForwardStatusTransition("pending", "in_progress")).not.toThrow()
|
||||||
|
expect(() => ensureForwardStatusTransition("in_progress", "completed")).not.toThrow()
|
||||||
|
expect(() => ensureForwardStatusTransition("in_progress", "pending")).toThrow(
|
||||||
|
"invalid_status_transition:in_progress->pending",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("requires blockers to be completed for in_progress/completed", () => {
|
||||||
|
//#given
|
||||||
|
const tasks = new Map<string, TeamTask>([
|
||||||
|
["done", createTask("done", "completed")],
|
||||||
|
["wait", createTask("wait", "pending")],
|
||||||
|
])
|
||||||
|
const readTask = (id: string) => tasks.get(id) ?? null
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(() => ensureDependenciesCompleted("pending", ["wait"], readTask)).not.toThrow()
|
||||||
|
expect(() => ensureDependenciesCompleted("in_progress", ["done"], readTask)).not.toThrow()
|
||||||
|
expect(() => ensureDependenciesCompleted("completed", ["wait"], readTask)).toThrow(
|
||||||
|
"blocked_by_incomplete:wait:pending",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
345
src/tools/agent-teams/tools.functional.test.ts
Normal file
345
src/tools/agent-teams/tools.functional.test.ts
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||||
|
import { existsSync, mkdtempSync, rmSync } from "node:fs"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import type { BackgroundManager } from "../../features/background-agent"
|
||||||
|
import { createAgentTeamsTools } from "./tools"
|
||||||
|
import { getTeamDir, getTeamInboxPath, getTeamTaskDir } from "./paths"
|
||||||
|
|
||||||
|
interface LaunchCall {
|
||||||
|
description: string
|
||||||
|
prompt: string
|
||||||
|
agent: string
|
||||||
|
parentSessionID: string
|
||||||
|
parentMessageID: string
|
||||||
|
parentAgent?: string
|
||||||
|
model?: {
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResumeCall {
|
||||||
|
sessionId: string
|
||||||
|
prompt: string
|
||||||
|
parentSessionID: string
|
||||||
|
parentMessageID: string
|
||||||
|
parentAgent?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CancelCall {
|
||||||
|
taskId: string
|
||||||
|
options?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MockManagerHandles {
|
||||||
|
manager: BackgroundManager
|
||||||
|
launchCalls: LaunchCall[]
|
||||||
|
resumeCalls: ResumeCall[]
|
||||||
|
cancelCalls: CancelCall[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestToolContext {
|
||||||
|
sessionID: string
|
||||||
|
messageID: string
|
||||||
|
agent: string
|
||||||
|
abort: AbortSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockManager(): MockManagerHandles {
|
||||||
|
const launchCalls: LaunchCall[] = []
|
||||||
|
const resumeCalls: ResumeCall[] = []
|
||||||
|
const cancelCalls: CancelCall[] = []
|
||||||
|
const launchedTasks = new Map<string, { id: string; sessionID: string }>()
|
||||||
|
let launchCount = 0
|
||||||
|
|
||||||
|
const manager = {
|
||||||
|
launch: async (args: LaunchCall) => {
|
||||||
|
launchCount += 1
|
||||||
|
launchCalls.push(args)
|
||||||
|
const task = { id: `bg-${launchCount}`, sessionID: `ses-worker-${launchCount}` }
|
||||||
|
launchedTasks.set(task.id, task)
|
||||||
|
return task
|
||||||
|
},
|
||||||
|
getTask: (taskId: string) => launchedTasks.get(taskId),
|
||||||
|
resume: async (args: ResumeCall) => {
|
||||||
|
resumeCalls.push(args)
|
||||||
|
return { id: `resume-${resumeCalls.length}` }
|
||||||
|
},
|
||||||
|
cancelTask: async (taskId: string, options?: unknown) => {
|
||||||
|
cancelCalls.push({ taskId, options })
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
} as unknown as BackgroundManager
|
||||||
|
|
||||||
|
return { manager, launchCalls, resumeCalls, cancelCalls }
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContext(): TestToolContext {
|
||||||
|
return {
|
||||||
|
sessionID: "ses-main",
|
||||||
|
messageID: "msg-main",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeJsonTool(
|
||||||
|
tools: ReturnType<typeof createAgentTeamsTools>,
|
||||||
|
toolName: keyof ReturnType<typeof createAgentTeamsTools>,
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
context: TestToolContext,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const output = await tools[toolName].execute(args, context)
|
||||||
|
return JSON.parse(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("agent-teams tools functional", () => {
|
||||||
|
let originalCwd: string
|
||||||
|
let tempProjectDir: string
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalCwd = process.cwd()
|
||||||
|
tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-tools-"))
|
||||||
|
process.chdir(tempProjectDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
rmSync(tempProjectDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team_create/read_config/delete work with project-local storage", async () => {
|
||||||
|
//#given
|
||||||
|
const { manager } = createMockManager()
|
||||||
|
const tools = createAgentTeamsTools(manager)
|
||||||
|
const context = createContext()
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const created = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_create",
|
||||||
|
{ team_name: "core", description: "Core team" },
|
||||||
|
context,
|
||||||
|
) as { team_name: string; team_file_path: string; lead_agent_id: string }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(created.team_name).toBe("core")
|
||||||
|
expect(created.lead_agent_id).toBe("team-lead@core")
|
||||||
|
expect(created.team_file_path).toBe(join(tempProjectDir, ".sisyphus", "agent-teams", "teams", "core", "config.json"))
|
||||||
|
expect(existsSync(created.team_file_path)).toBe(true)
|
||||||
|
expect(existsSync(getTeamInboxPath("core", "team-lead"))).toBe(true)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const config = await executeJsonTool(tools, "read_config", { team_name: "core" }, context) as {
|
||||||
|
name: string
|
||||||
|
members: Array<{ name: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(config.name).toBe("core")
|
||||||
|
expect(config.members.map((member) => member.name)).toEqual(["team-lead"])
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const deleted = await executeJsonTool(tools, "team_delete", { team_name: "core" }, context) as {
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(deleted.success).toBe(true)
|
||||||
|
expect(existsSync(getTeamDir("core"))).toBe(false)
|
||||||
|
expect(existsSync(getTeamTaskDir("core"))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("task tools create/update/get/list and emit assignment inbox message", async () => {
|
||||||
|
//#given
|
||||||
|
const { manager } = createMockManager()
|
||||||
|
const tools = createAgentTeamsTools(manager)
|
||||||
|
const context = createContext()
|
||||||
|
|
||||||
|
await executeJsonTool(tools, "team_create", { team_name: "core" }, context)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const createdTask = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_create",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
subject: "Draft release notes",
|
||||||
|
description: "Prepare release notes for next publish.",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { id: string; status: string }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(createdTask.id).toMatch(/^T-[a-f0-9-]+$/)
|
||||||
|
expect(createdTask.status).toBe("pending")
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const updatedTask = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_update",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
task_id: createdTask.id,
|
||||||
|
owner: "worker_1",
|
||||||
|
status: "in_progress",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { owner?: string; status: string }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(updatedTask.owner).toBe("worker_1")
|
||||||
|
expect(updatedTask.status).toBe("in_progress")
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const fetchedTask = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_get",
|
||||||
|
{ team_name: "core", task_id: createdTask.id },
|
||||||
|
context,
|
||||||
|
) as { id: string; owner?: string }
|
||||||
|
const listedTasks = await executeJsonTool(tools, "team_task_list", { team_name: "core" }, context) as Array<{ id: string }>
|
||||||
|
const inbox = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"read_inbox",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
agent_name: "worker_1",
|
||||||
|
unread_only: true,
|
||||||
|
mark_as_read: false,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as Array<{ summary?: string; text: string }>
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(fetchedTask.id).toBe(createdTask.id)
|
||||||
|
expect(fetchedTask.owner).toBe("worker_1")
|
||||||
|
expect(listedTasks.some((task) => task.id === createdTask.id)).toBe(true)
|
||||||
|
expect(inbox.some((message) => message.summary === "task_assignment")).toBe(true)
|
||||||
|
const assignment = inbox.find((message) => message.summary === "task_assignment")
|
||||||
|
expect(assignment).toBeDefined()
|
||||||
|
const payload = JSON.parse(assignment!.text) as { type: string; taskId: string }
|
||||||
|
expect(payload.type).toBe("task_assignment")
|
||||||
|
expect(payload.taskId).toBe(createdTask.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("spawn_teammate + send_message + force_kill_teammate execute end-to-end", async () => {
|
||||||
|
//#given
|
||||||
|
const { manager, launchCalls, resumeCalls, cancelCalls } = createMockManager()
|
||||||
|
const tools = createAgentTeamsTools(manager)
|
||||||
|
const context = createContext()
|
||||||
|
|
||||||
|
await executeJsonTool(tools, "team_create", { team_name: "core" }, context)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const spawned = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"spawn_teammate",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
name: "worker_1",
|
||||||
|
prompt: "Handle release prep",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { name: string; session_id: string; task_id: string }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(spawned.name).toBe("worker_1")
|
||||||
|
expect(spawned.session_id).toBe("ses-worker-1")
|
||||||
|
expect(spawned.task_id).toBe("bg-1")
|
||||||
|
expect(launchCalls).toHaveLength(1)
|
||||||
|
expect(launchCalls[0]).toMatchObject({
|
||||||
|
description: "[team:core] worker_1",
|
||||||
|
agent: "sisyphus-junior",
|
||||||
|
parentSessionID: "ses-main",
|
||||||
|
parentMessageID: "msg-main",
|
||||||
|
parentAgent: "sisyphus",
|
||||||
|
})
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const sent = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"send_message",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
type: "message",
|
||||||
|
recipient: "worker_1",
|
||||||
|
summary: "sync",
|
||||||
|
content: "Please update status.",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { success: boolean }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(sent.success).toBe(true)
|
||||||
|
expect(resumeCalls).toHaveLength(1)
|
||||||
|
expect(resumeCalls[0].sessionId).toBe("ses-worker-1")
|
||||||
|
|
||||||
|
//#given
|
||||||
|
const createdTask = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_create",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
subject: "Follow-up",
|
||||||
|
description: "Collect teammate update",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { id: string }
|
||||||
|
await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_update",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
task_id: createdTask.id,
|
||||||
|
owner: "worker_1",
|
||||||
|
status: "in_progress",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const killed = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"force_kill_teammate",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
agent_name: "worker_1",
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { success: boolean }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(killed.success).toBe(true)
|
||||||
|
expect(cancelCalls).toHaveLength(1)
|
||||||
|
expect(cancelCalls[0].taskId).toBe("bg-1")
|
||||||
|
expect(cancelCalls[0].options).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
source: "team_force_kill",
|
||||||
|
abortSession: true,
|
||||||
|
skipNotification: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const configAfterKill = await executeJsonTool(tools, "read_config", { team_name: "core" }, context) as {
|
||||||
|
members: Array<{ name: string }>
|
||||||
|
}
|
||||||
|
const taskAfterKill = await executeJsonTool(
|
||||||
|
tools,
|
||||||
|
"team_task_get",
|
||||||
|
{
|
||||||
|
team_name: "core",
|
||||||
|
task_id: createdTask.id,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
) as { owner?: string; status: string }
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(configAfterKill.members.some((member) => member.name === "worker_1")).toBe(false)
|
||||||
|
expect(taskAfterKill.owner).toBeUndefined()
|
||||||
|
expect(taskAfterKill.status).toBe("pending")
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user