fix(agent-teams): align spawn schema and harden inbox rollback behavior
This commit is contained in:
committed by
YeonGyu-Kim
parent
fe05a1f254
commit
40f844fb85
@@ -144,24 +144,28 @@ export function readInbox(
|
||||
return withInboxLock(teamName, () => {
|
||||
const messages = readInboxMessages(teamName, agentName)
|
||||
|
||||
const selected = unreadOnly ? messages.filter((message) => !message.read) : [...messages]
|
||||
const selectedIndexes = new Set<number>()
|
||||
const selected = unreadOnly
|
||||
? messages.filter((message, index) => {
|
||||
if (!message.read) {
|
||||
selectedIndexes.add(index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
: messages.map((message, index) => {
|
||||
selectedIndexes.add(index)
|
||||
return message
|
||||
})
|
||||
|
||||
if (!markAsRead || selected.length === 0) {
|
||||
return selected
|
||||
}
|
||||
|
||||
const selectedSet = unreadOnly ? new Set(selected) : null
|
||||
let changed = false
|
||||
|
||||
const updated = messages.map((message) => {
|
||||
if (!unreadOnly) {
|
||||
if (!message.read) {
|
||||
changed = true
|
||||
}
|
||||
return { ...message, read: true }
|
||||
}
|
||||
|
||||
if (selectedSet?.has(message)) {
|
||||
const updated = messages.map((message, index) => {
|
||||
if (selectedIndexes.has(index) && !message.read) {
|
||||
changed = true
|
||||
return { ...message, read: true }
|
||||
}
|
||||
@@ -171,7 +175,7 @@ export function readInbox(
|
||||
if (changed) {
|
||||
writeInboxMessages(teamName, agentName, updated)
|
||||
}
|
||||
return selected
|
||||
return updated.filter((_, index) => selectedIndexes.has(index))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -246,6 +246,8 @@ export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTe
|
||||
updateTeamConfig(params.teamName, (current) => upsertTeammate(current, nextMember))
|
||||
return nextMember
|
||||
} catch (error) {
|
||||
const originalError = error
|
||||
|
||||
if (launchedTaskID) {
|
||||
await params.manager
|
||||
.cancelTask(launchedTaskID, {
|
||||
@@ -255,9 +257,20 @@ export async function spawnTeammate(params: SpawnTeammateParams): Promise<TeamTe
|
||||
})
|
||||
.catch(() => undefined)
|
||||
}
|
||||
|
||||
try {
|
||||
updateTeamConfig(params.teamName, (current) => removeTeammate(current, params.name))
|
||||
} catch (cleanupError) {
|
||||
void cleanupError
|
||||
}
|
||||
|
||||
try {
|
||||
clearInbox(params.teamName, params.name)
|
||||
throw error
|
||||
} catch (cleanupError) {
|
||||
void cleanupError
|
||||
}
|
||||
|
||||
throw originalError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export function createSpawnTeammateTool(manager: BackgroundManager, options?: Ag
|
||||
team_name: tool.schema.string().describe("Team name"),
|
||||
name: tool.schema.string().describe("Teammate name"),
|
||||
prompt: tool.schema.string().describe("Initial teammate prompt"),
|
||||
category: tool.schema.string().optional().describe("Required category for teammate metadata and routing"),
|
||||
category: tool.schema.string().describe("Required category for teammate metadata and routing"),
|
||||
subagent_type: tool.schema.string().optional().describe("Agent name to run (default: sisyphus-junior)"),
|
||||
model: tool.schema.string().optional().describe("Optional model override in provider/model format"),
|
||||
plan_mode_required: tool.schema.boolean().optional().describe("Enable plan mode flag in teammate metadata"),
|
||||
@@ -46,11 +46,11 @@ export function createSpawnTeammateTool(manager: BackgroundManager, options?: Ag
|
||||
return JSON.stringify({ error: agentError })
|
||||
}
|
||||
|
||||
if (!input.category || !input.category.trim()) {
|
||||
if (!input.category.trim()) {
|
||||
return JSON.stringify({ error: "category_required" })
|
||||
}
|
||||
|
||||
if (input.category && input.subagent_type && input.subagent_type !== "sisyphus-junior") {
|
||||
if (input.subagent_type && input.subagent_type !== "sisyphus-junior") {
|
||||
return JSON.stringify({ error: "category_conflicts_with_subagent_type" })
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +363,8 @@ describe("agent-teams tools functional", () => {
|
||||
) as { error?: string }
|
||||
|
||||
//#then
|
||||
expect(result.error).toBe("category_required")
|
||||
expect(result.error).toBeDefined()
|
||||
expect(result.error).toContain("category")
|
||||
})
|
||||
|
||||
test("rejects category with incompatible subagent_type", async () => {
|
||||
@@ -931,6 +932,70 @@ describe("agent-teams tools functional", () => {
|
||||
expect(Array.isArray(ownInbox)).toBe(true)
|
||||
})
|
||||
|
||||
test("read_inbox returns messages with read=true when mark_as_read is enabled", async () => {
|
||||
//#given
|
||||
const { manager } = createMockManager()
|
||||
const tools = createAgentTeamsTools(manager)
|
||||
const context = createContext()
|
||||
|
||||
await executeJsonTool(tools, "team_create", { team_name: "core" }, context)
|
||||
await executeJsonTool(
|
||||
tools,
|
||||
"spawn_teammate",
|
||||
{
|
||||
team_name: "core",
|
||||
name: "worker_1",
|
||||
prompt: "Handle release prep",
|
||||
category: "quick",
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
//#when
|
||||
const unreadBefore = await executeJsonTool(
|
||||
tools,
|
||||
"read_inbox",
|
||||
{
|
||||
team_name: "core",
|
||||
agent_name: "worker_1",
|
||||
unread_only: true,
|
||||
mark_as_read: false,
|
||||
},
|
||||
context,
|
||||
) as Array<{ read: boolean }>
|
||||
|
||||
const markedRead = await executeJsonTool(
|
||||
tools,
|
||||
"read_inbox",
|
||||
{
|
||||
team_name: "core",
|
||||
agent_name: "worker_1",
|
||||
unread_only: true,
|
||||
mark_as_read: true,
|
||||
},
|
||||
context,
|
||||
) as Array<{ read: boolean }>
|
||||
|
||||
const unreadAfter = await executeJsonTool(
|
||||
tools,
|
||||
"read_inbox",
|
||||
{
|
||||
team_name: "core",
|
||||
agent_name: "worker_1",
|
||||
unread_only: true,
|
||||
mark_as_read: false,
|
||||
},
|
||||
context,
|
||||
) as Array<{ read: boolean }>
|
||||
|
||||
//#then
|
||||
expect(unreadBefore.length).toBeGreaterThan(0)
|
||||
expect(unreadBefore.every((message) => message.read === false)).toBe(true)
|
||||
expect(markedRead.length).toBeGreaterThan(0)
|
||||
expect(markedRead.every((message) => message.read === true)).toBe(true)
|
||||
expect(unreadAfter).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("rejects unknown session claiming team-lead identity", async () => {
|
||||
//#given
|
||||
const { manager } = createMockManager()
|
||||
|
||||
@@ -109,7 +109,7 @@ export const TeamSpawnInputSchema = z.object({
|
||||
team_name: z.string(),
|
||||
name: z.string(),
|
||||
prompt: z.string(),
|
||||
category: z.string().optional(),
|
||||
category: z.string(),
|
||||
subagent_type: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
plan_mode_required: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user