Files
Moltbot/src/agents/tools/sessions-spawn-tool.test.ts

190 lines
5.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const hoisted = vi.hoisted(() => {
const spawnSubagentDirectMock = vi.fn();
const spawnAcpDirectMock = vi.fn();
return {
spawnSubagentDirectMock,
spawnAcpDirectMock,
};
});
vi.mock("../subagent-spawn.js", () => ({
SUBAGENT_SPAWN_MODES: ["run", "session"],
spawnSubagentDirect: (...args: unknown[]) => hoisted.spawnSubagentDirectMock(...args),
}));
vi.mock("../acp-spawn.js", () => ({
ACP_SPAWN_MODES: ["run", "session"],
spawnAcpDirect: (...args: unknown[]) => hoisted.spawnAcpDirectMock(...args),
}));
const { createSessionsSpawnTool } = await import("./sessions-spawn-tool.js");
describe("sessions_spawn tool", () => {
beforeEach(() => {
hoisted.spawnSubagentDirectMock.mockReset().mockResolvedValue({
status: "accepted",
childSessionKey: "agent:main:subagent:1",
runId: "run-subagent",
});
hoisted.spawnAcpDirectMock.mockReset().mockResolvedValue({
status: "accepted",
childSessionKey: "agent:codex:acp:1",
runId: "run-acp",
});
});
it("uses subagent runtime by default", async () => {
const tool = createSessionsSpawnTool({
agentSessionKey: "agent:main:main",
agentChannel: "discord",
agentAccountId: "default",
agentTo: "channel:123",
agentThreadId: "456",
});
const result = await tool.execute("call-1", {
task: "build feature",
agentId: "main",
model: "anthropic/claude-sonnet-4-6",
thinking: "medium",
runTimeoutSeconds: 5,
thread: true,
mode: "session",
cleanup: "keep",
});
expect(result.details).toMatchObject({
status: "accepted",
childSessionKey: "agent:main:subagent:1",
runId: "run-subagent",
});
expect(hoisted.spawnSubagentDirectMock).toHaveBeenCalledWith(
expect.objectContaining({
task: "build feature",
agentId: "main",
model: "anthropic/claude-sonnet-4-6",
thinking: "medium",
runTimeoutSeconds: 5,
thread: true,
mode: "session",
cleanup: "keep",
}),
expect.objectContaining({
agentSessionKey: "agent:main:main",
}),
);
expect(hoisted.spawnAcpDirectMock).not.toHaveBeenCalled();
});
it("routes to ACP runtime when runtime=acp", async () => {
const tool = createSessionsSpawnTool({
agentSessionKey: "agent:main:main",
agentChannel: "discord",
agentAccountId: "default",
agentTo: "channel:123",
agentThreadId: "456",
});
const result = await tool.execute("call-2", {
runtime: "acp",
task: "investigate the failing CI run",
agentId: "codex",
cwd: "/workspace",
thread: true,
mode: "session",
});
expect(result.details).toMatchObject({
status: "accepted",
childSessionKey: "agent:codex:acp:1",
runId: "run-acp",
});
expect(hoisted.spawnAcpDirectMock).toHaveBeenCalledWith(
expect.objectContaining({
task: "investigate the failing CI run",
agentId: "codex",
cwd: "/workspace",
thread: true,
mode: "session",
}),
expect.objectContaining({
agentSessionKey: "agent:main:main",
}),
);
expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled();
});
it("forwards ACP sandbox options and requester sandbox context", async () => {
const tool = createSessionsSpawnTool({
agentSessionKey: "agent:main:subagent:parent",
sandboxed: true,
});
await tool.execute("call-2b", {
runtime: "acp",
task: "investigate",
agentId: "codex",
sandbox: "require",
});
expect(hoisted.spawnAcpDirectMock).toHaveBeenCalledWith(
expect.objectContaining({
task: "investigate",
sandbox: "require",
}),
expect.objectContaining({
agentSessionKey: "agent:main:subagent:parent",
sandboxed: true,
}),
);
});
it("rejects attachments for ACP runtime", async () => {
const tool = createSessionsSpawnTool({
agentSessionKey: "agent:main:main",
agentChannel: "discord",
agentAccountId: "default",
agentTo: "channel:123",
agentThreadId: "456",
});
const result = await tool.execute("call-3", {
runtime: "acp",
task: "analyze file",
attachments: [{ name: "a.txt", content: "hello", encoding: "utf8" }],
});
expect(result.details).toMatchObject({
status: "error",
});
const details = result.details as { error?: string };
expect(details.error).toContain("attachments are currently unsupported for runtime=acp");
expect(hoisted.spawnAcpDirectMock).not.toHaveBeenCalled();
expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled();
});
it("keeps attachment content schema unconstrained for llama.cpp grammar safety", () => {
const tool = createSessionsSpawnTool();
const schema = tool.parameters as {
properties?: {
attachments?: {
items?: {
properties?: {
content?: {
type?: string;
maxLength?: number;
};
};
};
};
};
};
const contentSchema = schema.properties?.attachments?.items?.properties?.content;
expect(contentSchema?.type).toBe("string");
expect(contentSchema?.maxLength).toBeUndefined();
});
});