Files
openclaw/extensions/acpx/src/runtime-elicitation.process.test.ts
Peter Steinberger 7e0b599ca4 fix: surface Codex input prompts across runtimes (#126387)
* fix: surface Codex input prompts across runtimes

Codex structured input now reaches bounded Gateway questions in native and ACP runs, with exact turn ownership, explicit unsupported outcomes, and cancellation fencing. Consume the published ACPX elicitation support.

* chore: align elicitation helpers with current guards

Use protocol-specific helper names required by current main and update the reservation regression to the generalized input owner.

* fix: formalize structured input SDK surface

Expose one documented, frozen agent-harness structured-input contract with runtime and subpath coverage, replacing the accidental function-property API.

* fix: satisfy elicitation architecture gates

Register the real-process ACPX fixture as an executable test root and move shared structured-input types into the boundary leaf to keep Knip and Madge clean.

* fix: remove structured input lint suppression

Preserve the rejected control and invisible-character ranges with an explicit code-point check so the production suppression inventory stays closed.
2026-08-19 15:25:33 -07:00

66 lines
2.2 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, expect, it, vi } from "vitest";
import { AcpxRuntime, createAgentRegistry, createFileSessionStore } from "./runtime.js";
const temporaryDirectories: string[] = [];
afterEach(async () => {
vi.unstubAllEnvs();
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => fs.rm(directory, { force: true, recursive: true })),
);
});
it("round-trips codex-acp request_user_input through real processes without empty answers", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acpx-elicitation-"));
temporaryDirectories.push(stateDir);
const codexAcpPath = path.resolve("node_modules/@agentclientprotocol/codex-acp/dist/index.js");
const appServerPath = path.resolve("extensions/acpx/test/fixtures/codex-app-server.mjs");
vi.stubEnv("CODEX_PATH", appServerPath);
const runtime = new AcpxRuntime({
cwd: process.cwd(),
sessionStore: createFileSessionStore({ stateDir }),
agentRegistry: createAgentRegistry({
overrides: { codex: [process.execPath, codexAcpPath] },
}),
permissionMode: "deny-all",
elicitationModes: ["form", "url"],
timeoutMs: 5_000,
});
const handle = await runtime.ensureSession({
sessionKey: "agent:elicitation:acp:process",
agent: "codex",
mode: "oneshot",
});
const onElicitation = vi.fn(async (_request, context) => {
expect(context.requestId).not.toBeUndefined();
expect(context.signal.aborted).toBe(false);
return { action: "accept" as const, content: { question: "round-trip" } };
});
const text: string[] = [];
try {
for await (const event of runtime.runTurn({
handle,
text: "ask",
mode: "prompt",
requestId: "process-turn",
onElicitation,
})) {
if (event.type === "text_delta") {
text.push(event.text);
}
}
} finally {
await runtime.close({ handle, reason: "test-complete" });
}
expect(onElicitation).toHaveBeenCalledOnce();
expect(text.join("")).toContain('"answers":{"question":{"answers":["round-trip"]}}');
expect(text.join("")).not.toContain('"answers":{}');
});