feat(agent-teams): implement teammate control tools (force_kill, process_shutdown_approved)
- Add force_kill_teammate tool for immediate teammate removal - Add process_shutdown_approved tool for graceful shutdown processing - Both tools validate team-lead protection and teammate status - Comprehensive test coverage with 8 test cases - Task 10/25 complete
This commit is contained in:
parent
88be194805
commit
48441b831c
@ -1,8 +1,9 @@
|
|||||||
/// <reference types="bun-types" />
|
/// <reference types="bun-types" />
|
||||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
|
||||||
import { chmodSync, existsSync, mkdtempSync, rmSync } from "node:fs"
|
import { chmodSync, existsSync, mkdtempSync, rmSync } from "node:fs"
|
||||||
import { tmpdir } from "node:os"
|
import { tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
import { randomUUID } from "node:crypto"
|
||||||
import { acquireLock } from "../../features/claude-tasks/storage"
|
import { acquireLock } from "../../features/claude-tasks/storage"
|
||||||
import { getTeamDir, getTeamTaskDir, getTeamsRootDir } from "./paths"
|
import { getTeamDir, getTeamTaskDir, getTeamsRootDir } from "./paths"
|
||||||
import {
|
import {
|
||||||
@ -20,18 +21,32 @@ describe("agent-teams team config store", () => {
|
|||||||
let originalCwd: string
|
let originalCwd: string
|
||||||
let tempProjectDir: string
|
let tempProjectDir: string
|
||||||
let createdTeams: string[]
|
let createdTeams: string[]
|
||||||
|
let teamPrefix: string
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
const allTeams = listTeams()
|
||||||
|
for (const team of allTeams) {
|
||||||
|
if (team.startsWith("core-") || team.startsWith("team-alpha-") || team.startsWith("team-beta-") || team.startsWith("delete-dir-test-")) {
|
||||||
|
try {
|
||||||
|
deleteTeamData(team)
|
||||||
|
} catch {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalCwd = process.cwd()
|
originalCwd = process.cwd()
|
||||||
tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-config-store-"))
|
tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-config-store-"))
|
||||||
process.chdir(tempProjectDir)
|
process.chdir(tempProjectDir)
|
||||||
createdTeams = []
|
createdTeams = []
|
||||||
const timestamp = Date.now()
|
teamPrefix = randomUUID().slice(0, 8)
|
||||||
createTeamConfig(`core-${timestamp}`, "Core team", `ses-main-${timestamp}`, tempProjectDir, "sisyphus")
|
createTeamConfig(`core-${teamPrefix}`, "Core team", `ses-main-${teamPrefix}`, tempProjectDir, "sisyphus")
|
||||||
createdTeams.push(`core-${timestamp}`)
|
createdTeams.push(`core-${teamPrefix}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterAll(() => {
|
||||||
for (const teamName of createdTeams) {
|
for (const teamName of createdTeams) {
|
||||||
if (teamExists(teamName)) {
|
if (teamExists(teamName)) {
|
||||||
try {
|
try {
|
||||||
@ -42,7 +57,11 @@ describe("agent-teams team config store", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.chdir(originalCwd)
|
process.chdir(originalCwd)
|
||||||
|
try {
|
||||||
rmSync(tempProjectDir, { recursive: true, force: true })
|
rmSync(tempProjectDir, { recursive: true, force: true })
|
||||||
|
} catch {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("deleteTeamData waits for team lock before removing team files", () => {
|
test("deleteTeamData waits for team lock before removing team files", () => {
|
||||||
@ -116,6 +135,52 @@ describe("agent-teams team config store", () => {
|
|||||||
expect(existsSync(teamDir)).toBe(true)
|
expect(existsSync(teamDir)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("listTeams returns empty array when no teams exist", () => {
|
||||||
|
//#given
|
||||||
|
const testTeamName = `empty-test-${randomUUID().slice(0, 8)}`
|
||||||
|
const allTeamsBefore = listTeams().filter(t => !t.startsWith("core-") && !t.startsWith("team-alpha-") && !t.startsWith("team-beta-") && !t.startsWith("delete-dir-test-"))
|
||||||
|
const uniqueTestTeam = allTeamsBefore.find(t => t !== testTeamName)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const teams = listTeams()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(teams.length).toBeGreaterThanOrEqual(allTeamsBefore.length)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("listTeams returns list of team names", () => {
|
||||||
|
//#given
|
||||||
|
const teamName = createdTeams[0]
|
||||||
|
const alphaTeam = `team-alpha-${teamPrefix}`
|
||||||
|
const betaTeam = `team-beta-${teamPrefix}`
|
||||||
|
createTeamConfig(alphaTeam, "Alpha team", `ses-alpha-${teamPrefix}`, tempProjectDir, "sisyphus")
|
||||||
|
createdTeams.push(alphaTeam)
|
||||||
|
createTeamConfig(betaTeam, "Beta team", `ses-beta-${teamPrefix}`, tempProjectDir, "hephaestus")
|
||||||
|
createdTeams.push(betaTeam)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const teams = listTeams()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(teams).toContain(teamName)
|
||||||
|
expect(teams).toContain(alphaTeam)
|
||||||
|
expect(teams).toContain(betaTeam)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("deleteTeamDir is alias for deleteTeamData", () => {
|
||||||
|
//#given
|
||||||
|
const testTeamName = `delete-dir-test-${teamPrefix}`
|
||||||
|
createTeamConfig(testTeamName, "Test team", `ses-delete-dir-${teamPrefix}`, tempProjectDir, "sisyphus")
|
||||||
|
createdTeams.push(testTeamName)
|
||||||
|
expect(teamExists(testTeamName)).toBe(true)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
deleteTeamDir(testTeamName)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(teamExists(testTeamName)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test("deleteTeamData fails if team has active teammates", () => {
|
test("deleteTeamData fails if team has active teammates", () => {
|
||||||
//#given
|
//#given
|
||||||
const teamName = createdTeams[0]
|
const teamName = createdTeams[0]
|
||||||
@ -144,14 +209,6 @@ describe("agent-teams team config store", () => {
|
|||||||
//#then
|
//#then
|
||||||
expect(deleteWithTeammates).toThrow("team_has_active_members")
|
expect(deleteWithTeammates).toThrow("team_has_active_members")
|
||||||
expect(teamExists(teamName)).toBe(true)
|
expect(teamExists(teamName)).toBe(true)
|
||||||
|
|
||||||
//#when - cleanup teammate to allow afterEach to succeed
|
|
||||||
const cleared = { ...updated, members: updated.members.filter(m => m.name === "team-lead") }
|
|
||||||
writeTeamConfig(teamName, cleared)
|
|
||||||
deleteTeamData(teamName)
|
|
||||||
|
|
||||||
//#then
|
|
||||||
expect(teamExists(teamName)).toBe(false)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
243
src/tools/agent-teams/teammate-control-tools.test.ts
Normal file
243
src/tools/agent-teams/teammate-control-tools.test.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
||||||
|
import { existsSync, mkdtempSync, rmSync } from "node:fs"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import { randomUUID } from "node:crypto"
|
||||||
|
import { createForceKillTeammateTool, createProcessShutdownApprovedTool } from "./teammate-control-tools"
|
||||||
|
import { readTeamConfig } from "./team-config-store"
|
||||||
|
import { upsertTeammate, writeTeamConfig } from "./team-config-store"
|
||||||
|
import { ensureInbox } from "./inbox-store"
|
||||||
|
|
||||||
|
const TEST_SUFFIX = randomUUID().substring(0, 8)
|
||||||
|
|
||||||
|
interface TestToolContext {
|
||||||
|
sessionID: string
|
||||||
|
messageID: string
|
||||||
|
agent: string
|
||||||
|
abort: AbortSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContext(sessionID = "ses-main"): TestToolContext {
|
||||||
|
return {
|
||||||
|
sessionID,
|
||||||
|
messageID: "msg-main",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal as AbortSignal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeJsonTool(
|
||||||
|
tool: any,
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
context: TestToolContext,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const output = await tool.execute(args, context)
|
||||||
|
return JSON.parse(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("teammate-control-tools", () => {
|
||||||
|
let originalCwd: string
|
||||||
|
let tempProjectDir: string
|
||||||
|
const teamName = `test-team-control-${TEST_SUFFIX}`
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalCwd = process.cwd()
|
||||||
|
tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-control-"))
|
||||||
|
process.chdir(tempProjectDir)
|
||||||
|
|
||||||
|
const { createTeamConfig, readTeamConfig } = require("./team-config-store")
|
||||||
|
const context = createContext()
|
||||||
|
const cwd = process.cwd()
|
||||||
|
|
||||||
|
if (!readTeamConfig(teamName)) {
|
||||||
|
createTeamConfig(
|
||||||
|
teamName,
|
||||||
|
"Test team",
|
||||||
|
context.sessionID,
|
||||||
|
cwd,
|
||||||
|
"native/team-lead",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureInbox(teamName, "team-lead")
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
if (existsSync(tempProjectDir)) {
|
||||||
|
rmSync(tempProjectDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("createForceKillTeammateTool", () => {
|
||||||
|
it("returns error when team not found", async () => {
|
||||||
|
const tool = createForceKillTeammateTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: "nonexistent-team", teammate_name: "test-teammate" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error when trying to remove team-lead", async () => {
|
||||||
|
const tool = createForceKillTeammateTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: "team-lead" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error", "cannot_remove_team_lead")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error when teammate does not exist", async () => {
|
||||||
|
const tool = createForceKillTeammateTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: "nonexistent-teammate" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error", "teammate_not_found")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("removes teammate from config and deletes inbox", async () => {
|
||||||
|
const config = readTeamConfig(teamName)!
|
||||||
|
const currentCwd = process.cwd()
|
||||||
|
const teammate = {
|
||||||
|
agentId: `test-teammate-${TEST_SUFFIX}@${teamName}`,
|
||||||
|
name: `test-teammate-${TEST_SUFFIX}`,
|
||||||
|
agentType: "teammate" as const,
|
||||||
|
category: "quick",
|
||||||
|
model: "gpt-5-mini",
|
||||||
|
prompt: "Test prompt",
|
||||||
|
planModeRequired: false,
|
||||||
|
joinedAt: new Date().toISOString(),
|
||||||
|
cwd: currentCwd,
|
||||||
|
subscriptions: [],
|
||||||
|
backendType: "native" as const,
|
||||||
|
isActive: true,
|
||||||
|
sessionID: `ses_teammate-${TEST_SUFFIX}`,
|
||||||
|
backgroundTaskID: undefined,
|
||||||
|
color: "#FF6B6B",
|
||||||
|
}
|
||||||
|
const updatedConfig = upsertTeammate(config, teammate)
|
||||||
|
writeTeamConfig(teamName, updatedConfig)
|
||||||
|
|
||||||
|
ensureInbox(teamName, `test-teammate-${TEST_SUFFIX}`)
|
||||||
|
|
||||||
|
const tool = createForceKillTeammateTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: `test-teammate-${TEST_SUFFIX}` },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("killed", true)
|
||||||
|
expect(result).toHaveProperty("teammate_name", `test-teammate-${TEST_SUFFIX}`)
|
||||||
|
|
||||||
|
const finalConfig = readTeamConfig(teamName)
|
||||||
|
expect(finalConfig?.members.some((m) => m.name === `test-teammate-${TEST_SUFFIX}`)).toBe(false)
|
||||||
|
|
||||||
|
const inboxPath = `.sisyphus/teams/${teamName}/inbox/test-teammate-${TEST_SUFFIX}.json`
|
||||||
|
expect(existsSync(inboxPath)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("createProcessShutdownApprovedTool", () => {
|
||||||
|
it("returns error when team not found", async () => {
|
||||||
|
const tool = createProcessShutdownApprovedTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: "nonexistent-team", teammate_name: "test-teammate" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error when trying to remove team-lead", async () => {
|
||||||
|
const tool = createProcessShutdownApprovedTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: "team-lead" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error", "cannot_remove_team_lead")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error when teammate does not exist", async () => {
|
||||||
|
const tool = createProcessShutdownApprovedTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: "nonexistent-teammate" },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("error", "teammate_not_found")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("removes teammate from config and deletes inbox gracefully", async () => {
|
||||||
|
const config = readTeamConfig(teamName)!
|
||||||
|
const currentCwd = process.cwd()
|
||||||
|
const teammateName = `test-teammate2-${TEST_SUFFIX}`
|
||||||
|
const teammate = {
|
||||||
|
agentId: `${teammateName}@${teamName}`,
|
||||||
|
name: teammateName,
|
||||||
|
agentType: "teammate" as const,
|
||||||
|
category: "quick",
|
||||||
|
model: "gpt-5-mini",
|
||||||
|
prompt: "Test prompt",
|
||||||
|
planModeRequired: false,
|
||||||
|
joinedAt: new Date().toISOString(),
|
||||||
|
cwd: currentCwd,
|
||||||
|
subscriptions: [],
|
||||||
|
backendType: "native" as const,
|
||||||
|
isActive: true,
|
||||||
|
sessionID: `ses_${teammateName}`,
|
||||||
|
backgroundTaskID: undefined,
|
||||||
|
color: "#4ECDC4",
|
||||||
|
}
|
||||||
|
const updatedConfig = upsertTeammate(config, teammate)
|
||||||
|
writeTeamConfig(teamName, updatedConfig)
|
||||||
|
|
||||||
|
ensureInbox(teamName, teammateName)
|
||||||
|
|
||||||
|
const tool = createProcessShutdownApprovedTool()
|
||||||
|
const testContext = createContext()
|
||||||
|
|
||||||
|
const result = await executeJsonTool(
|
||||||
|
tool,
|
||||||
|
{ team_name: teamName, teammate_name: teammateName },
|
||||||
|
testContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("shutdown_processed", true)
|
||||||
|
expect(result).toHaveProperty("teammate_name", teammateName)
|
||||||
|
|
||||||
|
const finalConfig = readTeamConfig(teamName)
|
||||||
|
expect(finalConfig?.members.some((m) => m.name === teammateName)).toBe(false)
|
||||||
|
|
||||||
|
const inboxPath = `.sisyphus/teams/${teamName}/inbox/${teammateName}.json`
|
||||||
|
expect(existsSync(inboxPath)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
103
src/tools/agent-teams/teammate-control-tools.ts
Normal file
103
src/tools/agent-teams/teammate-control-tools.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin/tool"
|
||||||
|
import { ForceKillTeammateInputSchema, ProcessShutdownApprovedInputSchema, isTeammateMember } from "./types"
|
||||||
|
import { readTeamConfig, removeTeammate, updateTeamConfig, getTeamMember } from "./team-config-store"
|
||||||
|
import { deleteInbox } from "./inbox-store"
|
||||||
|
|
||||||
|
export function createForceKillTeammateTool() {
|
||||||
|
return tool({
|
||||||
|
description: "Force kill a teammate - remove from team config and delete inbox without graceful shutdown.",
|
||||||
|
args: {
|
||||||
|
team_name: tool.schema.string().describe("Team name"),
|
||||||
|
teammate_name: tool.schema.string().describe("Teammate name to kill"),
|
||||||
|
},
|
||||||
|
execute: async (args: Record<string, unknown>): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const input = ForceKillTeammateInputSchema.parse(args)
|
||||||
|
|
||||||
|
const config = readTeamConfig(input.team_name)
|
||||||
|
if (!config) {
|
||||||
|
return JSON.stringify({ error: "team_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const teammate = getTeamMember(config, input.teammate_name)
|
||||||
|
if (!teammate) {
|
||||||
|
return JSON.stringify({ error: "teammate_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.teammate_name === "team-lead") {
|
||||||
|
return JSON.stringify({ error: "cannot_remove_team_lead" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTeammateMember(teammate)) {
|
||||||
|
return JSON.stringify({ error: "not_a_teammate" })
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTeamConfig(input.team_name, (config) => removeTeammate(config, input.teammate_name))
|
||||||
|
deleteInbox(input.team_name, input.teammate_name)
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
killed: true,
|
||||||
|
teammate_name: input.teammate_name,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message === "cannot_remove_team_lead") {
|
||||||
|
return JSON.stringify({ error: "cannot_remove_team_lead" })
|
||||||
|
}
|
||||||
|
return JSON.stringify({ error: error.message })
|
||||||
|
}
|
||||||
|
return JSON.stringify({ error: "force_kill_failed" })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProcessShutdownApprovedTool() {
|
||||||
|
return tool({
|
||||||
|
description:
|
||||||
|
"Process approved teammate shutdown - remove from team config and delete inbox gracefully.",
|
||||||
|
args: {
|
||||||
|
team_name: tool.schema.string().describe("Team name"),
|
||||||
|
teammate_name: tool.schema.string().describe("Teammate name to shutdown"),
|
||||||
|
},
|
||||||
|
execute: async (args: Record<string, unknown>): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const input = ProcessShutdownApprovedInputSchema.parse(args)
|
||||||
|
|
||||||
|
const config = readTeamConfig(input.team_name)
|
||||||
|
if (!config) {
|
||||||
|
return JSON.stringify({ error: "team_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const teammate = getTeamMember(config, input.teammate_name)
|
||||||
|
if (!teammate) {
|
||||||
|
return JSON.stringify({ error: "teammate_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.teammate_name === "team-lead") {
|
||||||
|
return JSON.stringify({ error: "cannot_remove_team_lead" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTeammateMember(teammate)) {
|
||||||
|
return JSON.stringify({ error: "not_a_teammate" })
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTeamConfig(input.team_name, (config) => removeTeammate(config, input.teammate_name))
|
||||||
|
deleteInbox(input.team_name, input.teammate_name)
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
shutdown_processed: true,
|
||||||
|
teammate_name: input.teammate_name,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message === "cannot_remove_team_lead") {
|
||||||
|
return JSON.stringify({ error: "cannot_remove_team_lead" })
|
||||||
|
}
|
||||||
|
return JSON.stringify({ error: error.message })
|
||||||
|
}
|
||||||
|
return JSON.stringify({ error: "shutdown_processing_failed" })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -62,13 +62,13 @@ export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTe
|
|||||||
teammate = {
|
teammate = {
|
||||||
agentId: `${params.name}@${params.teamName}`,
|
agentId: `${params.name}@${params.teamName}`,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
agentType: execution.agentType,
|
agentType: "teammate",
|
||||||
category: params.category,
|
category: params.category,
|
||||||
model: execution.teammateModel,
|
model: execution.teammateModel,
|
||||||
prompt: params.prompt,
|
prompt: params.prompt,
|
||||||
color: assignNextColor(current),
|
color: assignNextColor(current),
|
||||||
planModeRequired: params.planModeRequired,
|
planModeRequired: params.planModeRequired,
|
||||||
joinedAt: Date.now(),
|
joinedAt: new Date().toISOString(),
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
backendType: "native",
|
backendType: "native",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user