test: simplify ACP spawn scenarios

This commit is contained in:
Peter Steinberger 2026-03-22 15:10:21 -07:00
parent a81e671509
commit 030e950e5f
No known key found for this signature in database
1 changed files with 110 additions and 82 deletions

View File

@ -92,6 +92,14 @@ const resolveAcpSpawnStreamLogPathSpy = vi.spyOn(
);
const { spawnAcpDirect } = await import("./acp-spawn.js");
type SpawnRequest = Parameters<typeof spawnAcpDirect>[0];
type SpawnContext = Parameters<typeof spawnAcpDirect>[1];
type AgentCallParams = {
deliver?: boolean;
channel?: string;
to?: string;
threadId?: string;
};
function replaceSpawnConfig(next: OpenClawConfig): void {
const current = hoisted.state.cfg as Record<string, unknown>;
@ -153,6 +161,64 @@ function expectResolvedIntroTextInBindMetadata(): void {
expect(introText.includes("session ids: pending (available after the first reply)")).toBe(false);
}
function createSpawnRequest(overrides?: Partial<SpawnRequest>): SpawnRequest {
return {
task: "Investigate flaky tests",
agentId: "codex",
mode: "run",
...overrides,
};
}
function createRequesterContext(overrides?: Partial<SpawnContext>): SpawnContext {
return {
agentSessionKey: "agent:main:telegram:direct:6098642967",
agentChannel: "telegram",
agentAccountId: "default",
agentTo: "telegram:6098642967",
agentThreadId: "1",
...overrides,
};
}
function findAgentGatewayCall(): { method?: string; params?: Record<string, unknown> } | undefined {
return hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
}
function expectAgentGatewayCall(overrides: AgentCallParams): void {
const agentCall = findAgentGatewayCall();
expect(agentCall?.params?.deliver).toBe(overrides.deliver);
expect(agentCall?.params?.channel).toBe(overrides.channel);
expect(agentCall?.params?.to).toBe(overrides.to);
expect(agentCall?.params?.threadId).toBe(overrides.threadId);
}
function enableMatrixAcpThreadBindings(): void {
replaceSpawnConfig({
...hoisted.state.cfg,
channels: {
...hoisted.state.cfg.channels,
matrix: {
threadBindings: {
enabled: true,
spawnAcpSessions: true,
},
},
},
});
registerSessionBindingAdapter({
channel: "matrix",
accountId: "default",
capabilities: createSessionBindingCapabilities(),
bind: async (input) => await hoisted.sessionBindingBindMock(input),
listBySession: (targetSessionKey) => hoisted.sessionBindingListBySessionMock(targetSessionKey),
resolveByConversation: (ref) => hoisted.sessionBindingResolveByConversationMock(ref),
unbind: async (input) => await hoisted.sessionBindingUnbindMock(input),
});
}
describe("spawnAcpDirect", () => {
beforeEach(() => {
replaceSpawnConfig(createDefaultSpawnConfig());
@ -383,28 +449,7 @@ describe("spawnAcpDirect", () => {
});
it("spawns Matrix thread-bound ACP sessions from top-level room targets", async () => {
replaceSpawnConfig({
...hoisted.state.cfg,
channels: {
...hoisted.state.cfg.channels,
matrix: {
threadBindings: {
enabled: true,
spawnAcpSessions: true,
},
},
},
});
registerSessionBindingAdapter({
channel: "matrix",
accountId: "default",
capabilities: createSessionBindingCapabilities(),
bind: async (input) => await hoisted.sessionBindingBindMock(input),
listBySession: (targetSessionKey) =>
hoisted.sessionBindingListBySessionMock(targetSessionKey),
resolveByConversation: (ref) => hoisted.sessionBindingResolveByConversationMock(ref),
unbind: async (input) => await hoisted.sessionBindingUnbindMock(input),
});
enableMatrixAcpThreadBindings();
hoisted.sessionBindingBindMock.mockImplementationOnce(
async (input: {
targetSessionKey: string;
@ -453,74 +498,57 @@ describe("spawnAcpDirect", () => {
}),
}),
);
const agentCall = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
expect(agentCall?.params?.channel).toBe("matrix");
expect(agentCall?.params?.to).toBe("room:!room:example");
expect(agentCall?.params?.threadId).toBe("child-thread");
expectAgentGatewayCall({
deliver: true,
channel: "matrix",
to: "room:!room:example",
threadId: "child-thread",
});
});
it("inlines delivery for run-mode spawns from non-subagent requester sessions", async () => {
const result = await spawnAcpDirect(
{
task: "Investigate flaky tests",
agentId: "codex",
mode: "run",
},
{
agentSessionKey: "agent:main:telegram:direct:6098642967",
agentChannel: "telegram",
agentAccountId: "default",
agentTo: "telegram:6098642967",
agentThreadId: "1",
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
expect(hoisted.resolveSessionTranscriptFileMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: "sess-123",
storePath: "/tmp/codex-sessions.json",
agentId: "codex",
}),
);
const agentCall = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
expect(agentCall?.params?.deliver).toBe(true);
expect(agentCall?.params?.channel).toBe("telegram");
expect(agentCall?.params?.to).toBe("telegram:6098642967");
});
it("does not inline delivery for run-mode spawns from subagent requester sessions", async () => {
const result = await spawnAcpDirect(
{
task: "Investigate flaky tests",
agentId: "codex",
mode: "run",
},
{
it.each([
{
name: "inlines delivery for run-mode spawns from non-subagent requester sessions",
ctx: createRequesterContext(),
expectedAgentCall: {
deliver: true,
channel: "telegram",
to: "telegram:6098642967",
threadId: "1",
} satisfies AgentCallParams,
expectTranscriptPersistence: true,
},
{
name: "does not inline delivery for run-mode spawns from subagent requester sessions",
ctx: createRequesterContext({
agentSessionKey: "agent:main:subagent:orchestrator",
agentChannel: "telegram",
agentAccountId: "default",
agentTo: "telegram:6098642967",
},
);
agentThreadId: undefined,
}),
expectedAgentCall: {
deliver: false,
channel: undefined,
to: undefined,
threadId: undefined,
} satisfies AgentCallParams,
expectTranscriptPersistence: false,
},
])("$name", async ({ ctx, expectedAgentCall, expectTranscriptPersistence }) => {
const result = await spawnAcpDirect(createSpawnRequest(), ctx);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
const agentCall = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
expect(agentCall?.params?.deliver).toBe(false);
expect(agentCall?.params?.channel).toBeUndefined();
expect(agentCall?.params?.to).toBeUndefined();
if (expectTranscriptPersistence) {
expect(hoisted.resolveSessionTranscriptFileMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: "sess-123",
storePath: "/tmp/codex-sessions.json",
agentId: "codex",
}),
);
}
expectAgentGatewayCall(expectedAgentCall);
});
it("keeps ACP spawn running when session-file persistence fails", async () => {