Files
openclaw/extensions/acpx/test/fixtures/codex-app-server.mjs
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

137 lines
3.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
import readline from "node:readline";
let nextRequestId = 1;
const pending = new Map();
function write(message) {
process.stdout.write(`${JSON.stringify(message)}\n`);
}
function notify(method, params) {
write({ method, params });
}
function request(method, params) {
const id = `fixture-${nextRequestId++}`;
write({ id, method, params });
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
});
}
const model = {
id: "gpt-5.6-luna",
model: "gpt-5.6-luna",
displayName: "GPT-5.6 Luna",
description: "Process-fixture model",
hidden: false,
isDefault: true,
defaultReasoningEffort: "medium",
supportedReasoningEfforts: [],
inputModalities: ["text"],
};
async function handle(method, params) {
if (method === "initialize") {
return { userAgent: "openclaw-acpx-process-fixture", codexHome: process.cwd() };
}
if (method === "account/read") {
return { requiresOpenaiAuth: false, account: null };
}
if (method === "config/read") {
return { config: {} };
}
if (method === "skills/list") {
return { data: [] };
}
if (method === "model/list") {
return { data: [model], nextCursor: null };
}
if (method === "thread/start") {
return {
thread: { id: "thread-process", name: "Process fixture", preview: "", cwd: process.cwd() },
model: model.id,
modelProvider: "openai",
reasoningEffort: "medium",
serviceTier: null,
};
}
if (method === "turn/start") {
const turnId = "turn-process";
const turn = { id: turnId, items: [], status: "inProgress", error: null };
queueMicrotask(() => {
void (async () => {
notify("turn/started", { threadId: params.threadId, turn });
const answers = await request("item/tool/requestUserInput", {
threadId: params.threadId,
turnId,
itemId: "request_user_input",
questions: [
{
id: "question",
header: "Answer",
question: "Choose a value",
isOther: false,
isSecret: false,
options: null,
},
],
isBlocking: true,
autoResolutionMs: null,
});
const text = JSON.stringify(answers);
const item = { type: "agentMessage", id: "message-process", text };
notify("item/agentMessage/delta", {
threadId: params.threadId,
turnId,
itemId: item.id,
delta: text,
});
notify("item/completed", { threadId: params.threadId, turnId, item });
notify("turn/completed", {
threadId: params.threadId,
turn: { ...turn, items: [item], status: "completed" },
});
})().catch(() => {
process.stderr.write("codex app-server fixture turn failed\n");
});
});
return { turn };
}
if (["turn/interrupt", "thread/unsubscribe", "thread/archive"].includes(method)) {
return {};
}
throw new Error(`unsupported fixture method: ${method}`);
}
readline.createInterface({ input: process.stdin }).on("line", async (line) => {
let message;
try {
message = JSON.parse(line);
} catch {
return;
}
if (message && "id" in message && !("method" in message)) {
const waiter = pending.get(message.id);
pending.delete(message.id);
if (message.error) {
waiter?.reject(new Error(message.error.message ?? "fixture request failed"));
} else {
waiter?.resolve(message.result);
}
return;
}
if (!message || typeof message.method !== "string" || !("id" in message)) {
return;
}
try {
write({ id: message.id, result: await handle(message.method, message.params ?? {}) });
} catch (error) {
write({
id: message.id,
error: { code: -32601, message: error instanceof Error ? error.message : String(error) },
});
}
});