Files
openclaw/src/plugin-sdk/acp-runtime.test.ts
Onur Solmaz 154a7edb7c refactor: consume acpx runtime library (#61495)
* refactor: consume acpx runtime library

* refactor: remove duplicated acpx runtime files

* fix: update acpx runtime dependency

* fix: preserve acp runtime error codes

* fix: migrate legacy acpx session files

* fix: update acpx runtime dependency

* fix: import Dirent from node fs

* ACPX: repin shared runtime engine

* ACPX: repin runtime semantics fixes

* ACPX: repin runtime contract cleanup

* Extensions: repin ACPX after layout refactor

* ACPX: drop legacy session migration

* ACPX: drop direct ACP SDK dependency

* Discord ACP: stop duplicate direct fallback replies

* ACP: rename delivered text visibility hook

* ACPX: pin extension to 0.5.0

* Deps: drop stale ACPX build-script allowlist

* ACPX: add local development guidance

* ACPX: document temporary pnpm exception flow

* SDK: preserve legacy ACP visibility hook

* ACP: keep reset commands on local path

* ACP: make in-place reset start fresh session

* ACP: recover broken bindings on fresh reset

* ACP: defer fresh reset marker until close succeeds

* ACP: reset bound sessions fresh again

* Discord: ensure ACP bindings before /new

* ACP: recover missing persistent sessions
2026-04-06 15:51:08 +02:00

145 lines
3.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildTestCtx } from "../auto-reply/reply/test-ctx.js";
const { bypassMock, dispatchMock } = vi.hoisted(() => ({
bypassMock: vi.fn(),
dispatchMock: vi.fn(),
}));
vi.mock("../auto-reply/reply/dispatch-acp.runtime.js", () => ({
shouldBypassAcpDispatchForCommand: bypassMock,
tryDispatchAcpReply: dispatchMock,
}));
import { tryDispatchAcpReplyHook } from "./acp-runtime.js";
const event = {
ctx: buildTestCtx({
SessionKey: "agent:test:session",
CommandBody: "/acp cancel",
BodyForCommands: "/acp cancel",
BodyForAgent: "/acp cancel",
}),
runId: "run-1",
sessionKey: "agent:test:session",
inboundAudio: false,
sessionTtsAuto: "off" as const,
ttsChannel: undefined,
suppressUserDelivery: false,
shouldRouteToOriginating: false,
originatingChannel: undefined,
originatingTo: undefined,
shouldSendToolSummaries: true,
sendPolicy: "allow" as const,
};
const ctx = {
cfg: {},
dispatcher: {
sendToolResult: () => false,
sendBlockReply: () => false,
sendFinalReply: () => false,
waitForIdle: async () => {},
getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
getFailedCounts: () => ({ tool: 0, block: 0, final: 0 }),
markComplete: () => {},
},
abortSignal: undefined,
onReplyStart: undefined,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
};
describe("tryDispatchAcpReplyHook", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("skips ACP runtime lookup for plain-text deny turns", async () => {
const result = await tryDispatchAcpReplyHook(
{
...event,
sendPolicy: "deny",
ctx: buildTestCtx({
SessionKey: "agent:test:session",
BodyForCommands: "write a test",
BodyForAgent: "write a test",
}),
},
ctx,
);
expect(result).toBeUndefined();
expect(bypassMock).not.toHaveBeenCalled();
expect(dispatchMock).not.toHaveBeenCalled();
});
it("skips ACP dispatch when send policy denies delivery and no bypass applies", async () => {
bypassMock.mockResolvedValue(false);
const result = await tryDispatchAcpReplyHook({ ...event, sendPolicy: "deny" }, ctx);
expect(result).toBeUndefined();
expect(dispatchMock).not.toHaveBeenCalled();
});
it("dispatches through ACP when command bypass applies", async () => {
bypassMock.mockResolvedValue(true);
dispatchMock.mockResolvedValue({
queuedFinal: true,
counts: { tool: 1, block: 2, final: 3 },
});
const result = await tryDispatchAcpReplyHook({ ...event, sendPolicy: "deny" }, ctx);
expect(result).toEqual({
handled: true,
queuedFinal: true,
counts: { tool: 1, block: 2, final: 3 },
});
expect(dispatchMock).toHaveBeenCalledWith(
expect.objectContaining({
ctx: event.ctx,
cfg: ctx.cfg,
dispatcher: ctx.dispatcher,
bypassForCommand: true,
}),
);
});
it("returns unhandled when ACP dispatcher declines the turn", async () => {
bypassMock.mockResolvedValue(false);
dispatchMock.mockResolvedValue(undefined);
const result = await tryDispatchAcpReplyHook(event, ctx);
expect(result).toBeUndefined();
expect(dispatchMock).toHaveBeenCalledOnce();
});
it("does not let ACP claim reset commands before local command handling", async () => {
bypassMock.mockResolvedValue(true);
dispatchMock.mockResolvedValue(undefined);
const result = await tryDispatchAcpReplyHook(
{
...event,
ctx: buildTestCtx({
SessionKey: "agent:test:session",
CommandBody: "/new",
BodyForCommands: "/new",
BodyForAgent: "/new",
}),
},
ctx,
);
expect(result).toBeUndefined();
expect(dispatchMock).toHaveBeenCalledWith(
expect.objectContaining({
bypassForCommand: true,
}),
);
});
});