fix(call-omo-agent): address cubic review findings and add requirement-based tests
- Fix agent-resolver.ts: add defensive validation on agent name (typeof, trim, filter)
- Fix tools.test.ts: correct mock to return {data: agents} matching SDK contract
- Fix agent-config-handler.ts: include opencode global/project agents in customAgentSummaries
- Add agent-resolver.test.ts: 14 requirement-based tests covering R1-R7 behavioral specs
- Add tools-edge-cases.test.ts: 5 integration tests for rollback, whitespace, dedup, session_id
This commit is contained in:
committed by
YeonGyu-Kim
parent
76c5356a80
commit
da91c53536
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Requirement-based integration tests for createCallOmoAgent edge cases
|
||||
* introduced by the dev rebase and dynamic agent resolution feature.
|
||||
*
|
||||
* R1: Spawn reservation is rolled back when execution fails after reservation
|
||||
* R2: Agent names with leading/trailing whitespace are trimmed before matching
|
||||
* R3: An agent present in both ALLOWED_AGENTS and dynamic list is callable (no conflict)
|
||||
* R4: session_id continuation rejects in background mode when session already exists
|
||||
*/
|
||||
const { describe, test, expect, mock, beforeEach } = require("bun:test")
|
||||
const { createCallOmoAgent } = require("./tools")
|
||||
|
||||
type PluginInput = { client: any; directory: string }
|
||||
|
||||
function createMockCtx(agents: Array<{ name: string; mode?: string }> = []): PluginInput {
|
||||
return {
|
||||
client: {
|
||||
app: {
|
||||
agents: mock(() => Promise.resolve({ data: agents })),
|
||||
},
|
||||
},
|
||||
directory: "/test",
|
||||
} as unknown as PluginInput
|
||||
}
|
||||
|
||||
const DEFAULT_AGENTS = [
|
||||
{ name: "explore", mode: "subagent" },
|
||||
{ name: "librarian", mode: "subagent" },
|
||||
{ name: "oracle", mode: "subagent" },
|
||||
{ name: "hephaestus", mode: "subagent" },
|
||||
{ name: "metis", mode: "subagent" },
|
||||
{ name: "momus", mode: "subagent" },
|
||||
{ name: "multimodal-looker", mode: "subagent" },
|
||||
]
|
||||
|
||||
const reserveCommitMock = mock(() => 1)
|
||||
const reserveRollbackMock = mock(() => {})
|
||||
const reserveSubagentSpawnMock = mock(() => Promise.resolve({
|
||||
spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 },
|
||||
descendantCount: 1,
|
||||
commit: reserveCommitMock,
|
||||
rollback: reserveRollbackMock,
|
||||
}))
|
||||
|
||||
const toolCtx = {
|
||||
sessionID: "test",
|
||||
messageID: "msg",
|
||||
agent: "test",
|
||||
abort: new AbortController().signal,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
reserveSubagentSpawnMock.mockClear()
|
||||
reserveCommitMock.mockClear()
|
||||
reserveRollbackMock.mockClear()
|
||||
})
|
||||
|
||||
describe("createCallOmoAgent edge cases", () => {
|
||||
describe("#given spawn reservation succeeds but sync execution fails", () => {
|
||||
test("#then rollback is called to release the reservation", async () => {
|
||||
const mockCtx = createMockCtx(DEFAULT_AGENTS)
|
||||
reserveSubagentSpawnMock.mockResolvedValueOnce({
|
||||
spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 },
|
||||
descendantCount: 1,
|
||||
commit: reserveCommitMock,
|
||||
rollback: reserveRollbackMock,
|
||||
})
|
||||
const mockManager = {
|
||||
assertCanSpawn: mock(() => Promise.resolve(undefined)),
|
||||
reserveSubagentSpawn: reserveSubagentSpawnMock,
|
||||
launch: mock(() => Promise.resolve()),
|
||||
getTask: mock(() => undefined),
|
||||
}
|
||||
const toolDef = createCallOmoAgent(mockCtx, mockManager, [])
|
||||
const executeFunc = toolDef.execute as Function
|
||||
|
||||
const result = await executeFunc(
|
||||
{
|
||||
description: "Test",
|
||||
prompt: "Test prompt",
|
||||
subagent_type: "explore",
|
||||
run_in_background: false,
|
||||
},
|
||||
toolCtx,
|
||||
)
|
||||
|
||||
expect(reserveRollbackMock).toHaveBeenCalled()
|
||||
expect(result).toContain("Error:")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given agent names with extra whitespace from SDK", () => {
|
||||
test("#then whitespace-padded names are trimmed and matched correctly", async () => {
|
||||
const agents = [
|
||||
...DEFAULT_AGENTS,
|
||||
{ name: " bug-fixer ", mode: "subagent" },
|
||||
]
|
||||
const mockCtx = createMockCtx(agents)
|
||||
const mockManager = {
|
||||
assertCanSpawn: mock(() => Promise.resolve(undefined)),
|
||||
reserveSubagentSpawn: reserveSubagentSpawnMock,
|
||||
launch: mock(() => Promise.resolve({
|
||||
id: "task-id",
|
||||
sessionID: "ses-1",
|
||||
description: "Test",
|
||||
agent: "bug-fixer",
|
||||
status: "pending",
|
||||
})),
|
||||
getTask: mock(() => ({ status: "pending", sessionID: "ses-1" })),
|
||||
}
|
||||
const toolDef = createCallOmoAgent(mockCtx, mockManager, [])
|
||||
const executeFunc = toolDef.execute as Function
|
||||
|
||||
const result = await executeFunc(
|
||||
{
|
||||
description: "Test",
|
||||
prompt: "Fix bug",
|
||||
subagent_type: "bug-fixer",
|
||||
run_in_background: true,
|
||||
},
|
||||
toolCtx,
|
||||
)
|
||||
|
||||
expect(result).not.toContain("Invalid agent type")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given an agent exists in both ALLOWED_AGENTS and dynamic results", () => {
|
||||
test("#then the agent is callable without conflict", async () => {
|
||||
const agents = [
|
||||
...DEFAULT_AGENTS,
|
||||
{ name: "explore", mode: "subagent" },
|
||||
]
|
||||
const mockCtx = createMockCtx(agents)
|
||||
const mockManager = {
|
||||
assertCanSpawn: mock(() => Promise.resolve(undefined)),
|
||||
reserveSubagentSpawn: reserveSubagentSpawnMock,
|
||||
launch: mock(() => Promise.resolve({
|
||||
id: "task-id",
|
||||
sessionID: "ses-1",
|
||||
description: "Test",
|
||||
agent: "explore",
|
||||
status: "pending",
|
||||
})),
|
||||
getTask: mock(() => ({ status: "pending", sessionID: "ses-1" })),
|
||||
}
|
||||
const toolDef = createCallOmoAgent(mockCtx, mockManager, [])
|
||||
const executeFunc = toolDef.execute as Function
|
||||
|
||||
const result = await executeFunc(
|
||||
{
|
||||
description: "Test",
|
||||
prompt: "Search codebase",
|
||||
subagent_type: "explore",
|
||||
run_in_background: true,
|
||||
},
|
||||
toolCtx,
|
||||
)
|
||||
|
||||
expect(result).not.toContain("Invalid agent type")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given a disabled custom agent from dynamic resolution", () => {
|
||||
test("#then disabled_agents check takes precedence over dynamic availability", async () => {
|
||||
const agents = [
|
||||
...DEFAULT_AGENTS,
|
||||
{ name: "bug-fixer", mode: "subagent" },
|
||||
]
|
||||
const mockCtx = createMockCtx(agents)
|
||||
const mockManager = {
|
||||
assertCanSpawn: mock(() => Promise.resolve(undefined)),
|
||||
reserveSubagentSpawn: reserveSubagentSpawnMock,
|
||||
launch: mock(() => Promise.resolve()),
|
||||
getTask: mock(() => undefined),
|
||||
}
|
||||
const toolDef = createCallOmoAgent(mockCtx, mockManager, ["Bug-Fixer"])
|
||||
const executeFunc = toolDef.execute as Function
|
||||
|
||||
const result = await executeFunc(
|
||||
{
|
||||
description: "Test",
|
||||
prompt: "Fix bug",
|
||||
subagent_type: "bug-fixer",
|
||||
run_in_background: true,
|
||||
},
|
||||
toolCtx,
|
||||
)
|
||||
|
||||
expect(result).toContain("disabled via disabled_agents")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given session_id is provided in background mode", () => {
|
||||
test("#then the request is rejected with a clear error", async () => {
|
||||
const mockCtx = createMockCtx(DEFAULT_AGENTS)
|
||||
const mockManager = {
|
||||
assertCanSpawn: mock(() => Promise.resolve(undefined)),
|
||||
reserveSubagentSpawn: reserveSubagentSpawnMock,
|
||||
launch: mock(() => Promise.resolve()),
|
||||
getTask: mock(() => undefined),
|
||||
}
|
||||
const toolDef = createCallOmoAgent(mockCtx, mockManager, [])
|
||||
const executeFunc = toolDef.execute as Function
|
||||
|
||||
const result = await executeFunc(
|
||||
{
|
||||
description: "Test",
|
||||
prompt: "Continue work",
|
||||
subagent_type: "explore",
|
||||
run_in_background: true,
|
||||
session_id: "ses-existing-123",
|
||||
},
|
||||
toolCtx,
|
||||
)
|
||||
|
||||
expect(result).toContain("session_id is not supported in background mode")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export {}
|
||||
Reference in New Issue
Block a user