Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
const { runtimeRegistry } = vi.hoisted(() => ({
runtimeRegistry: new Map<string, { runtime: unknown; healthy?: () => boolean }>(),
}));
const { prepareAcpxCodexAuthConfigMock } = vi.hoisted(() => ({
prepareAcpxCodexAuthConfigMock: vi.fn(
async ({ pluginConfig }: { pluginConfig: unknown }) => pluginConfig,
),
}));
vi.mock("../runtime-api.js", () => ({
getAcpRuntimeBackend: (id: string) => runtimeRegistry.get(id),
registerAcpRuntimeBackend: (entry: { id: string; runtime: unknown; healthy?: () => boolean }) => {
runtimeRegistry.set(entry.id, entry);
},
unregisterAcpRuntimeBackend: (id: string) => {
runtimeRegistry.delete(id);
},
}));
vi.mock("./runtime.js", () => ({
ACPX_BACKEND_ID: "acpx",
AcpxRuntime: function AcpxRuntime() {},
createAgentRegistry: vi.fn(() => ({})),
createFileSessionStore: vi.fn(() => ({})),
}));
vi.mock("./codex-auth-bridge.js", () => ({
prepareAcpxCodexAuthConfig: prepareAcpxCodexAuthConfigMock,
}));
import { getAcpRuntimeBackend } from "../runtime-api.js";
import { createAcpxRuntimeService } from "./service.js";
const tempDirs: string[] = [];
async function makeTempDir(): Promise<string> {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acpx-service-"));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
runtimeRegistry.clear();
prepareAcpxCodexAuthConfigMock.mockClear();
delete process.env.OPENCLAW_SKIP_ACPX_RUNTIME;
delete process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE;
for (const dir of tempDirs.splice(0)) {
await fs.rm(dir, { recursive: true, force: true });
}
});
function createServiceContext(workspaceDir: string) {
return {
workspaceDir,
stateDir: path.join(workspaceDir, ".openclaw-plugin-state"),
config: {},
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
};
}
function createMockRuntime(overrides: Record<string, unknown> = {}) {
return {
ensureSession: vi.fn(),
runTurn: vi.fn(),
cancel: vi.fn(),
close: vi.fn(),
probeAvailability: vi.fn(async () => {}),
isHealthy: vi.fn(() => true),
doctor: vi.fn(async () => ({ ok: true, message: "ok" })),
...overrides,
};
}
describe("createAcpxRuntimeService", () => {
it("registers and unregisters the embedded backend", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime();
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(getAcpRuntimeBackend("acpx")?.runtime).toBe(runtime);
await service.stop?.(ctx);
expect(getAcpRuntimeBackend("acpx")).toBeUndefined();
});
it("creates the embedded runtime state directory before probing", async () => {
const workspaceDir = await makeTempDir();
const stateDir = path.join(workspaceDir, "custom-state");
const ctx = createServiceContext(workspaceDir);
const probeAvailability = vi.fn(async () => {
await fs.access(stateDir);
});
const runtime = createMockRuntime({
doctor: async () => ({ ok: true, message: "ok" }),
isHealthy: () => true,
probeAvailability,
});
const service = createAcpxRuntimeService({
pluginConfig: { stateDir },
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(probeAvailability).toHaveBeenCalledOnce();
await service.stop?.(ctx);
});
it("passes the default runtime timeout to the embedded runtime factory", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
timeoutSeconds: 120,
}),
}),
);
await service.stop?.(ctx);
});
it("forwards a configured probeAgent to the runtime factory so the probe does not hardcode the default", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = {
ensureSession: vi.fn(),
runTurn: vi.fn(),
cancel: vi.fn(),
close: vi.fn(),
probeAvailability: vi.fn(async () => {}),
isHealthy: vi.fn(() => true),
doctor: vi.fn(async () => ({ ok: true, message: "ok" })),
};
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
pluginConfig: { probeAgent: "opencode" },
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "opencode",
}),
}),
);
await service.stop?.(ctx);
});
it("uses the first allowed ACP agent as the default probe agent", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: [" OpenCode ", "codex"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "opencode",
}),
}),
);
await service.stop?.(ctx);
});
it("keeps explicit probeAgent ahead of acp.allowedAgents", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: ["opencode"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
pluginConfig: { probeAgent: "codex" },
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "codex",
}),
}),
);
await service.stop?.(ctx);
});
it("warns when legacy compatibility config is explicitly ignored", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime();
const service = createAcpxRuntimeService({
pluginConfig: {
queueOwnerTtlSeconds: 30,
strictWindowsCmdWrapper: false,
},
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(ctx.logger.warn).toHaveBeenCalledWith(
expect.stringContaining(
"embedded acpx runtime ignores legacy compatibility config: queueOwnerTtlSeconds, strictWindowsCmdWrapper=false",
),
);
await service.stop?.(ctx);
});
it("can skip the embedded runtime probe via env", async () => {
process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const probeAvailability = vi.fn(async () => {});
const runtime = createMockRuntime({
doctor: async () => ({ ok: false, message: "nope" }),
isHealthy: () => false,
probeAvailability,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(probeAvailability).not.toHaveBeenCalled();
expect(getAcpRuntimeBackend("acpx")).toBeTruthy();
await service.stop?.(ctx);
});
it("formats non-string doctor details without losing object payloads", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime({
doctor: async () => ({
ok: false,
message: "probe failed",
details: [{ code: "ACP_CLOSED", agent: "codex" }, new Error("stdin closed")],
}),
isHealthy: () => false,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
await vi.waitFor(() => {
expect(ctx.logger.warn).toHaveBeenCalledWith(
'embedded acpx runtime backend probe failed: probe failed ({"code":"ACP_CLOSED","agent":"codex"}; stdin closed)',
);
});
await service.stop?.(ctx);
});
it("can skip the embedded runtime backend via env", async () => {
process.env.OPENCLAW_SKIP_ACPX_RUNTIME = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtimeFactory = vi.fn(() => {
throw new Error("runtime factory should not run when ACPX is skipped");
});
const service = createAcpxRuntimeService({
runtimeFactory: runtimeFactory as never,
});
await service.start(ctx);
expect(runtimeFactory).not.toHaveBeenCalled();
expect(getAcpRuntimeBackend("acpx")).toBeUndefined();
expect(ctx.logger.info).toHaveBeenCalledWith(
"skipping embedded acpx runtime backend (OPENCLAW_SKIP_ACPX_RUNTIME=1)",
);
});
});
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|