mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-18 12:08:52 +00:00
test(release): route Codex live slash checks through chat
This commit is contained in:
@@ -20,6 +20,7 @@ import { getFreePortBlockWithPermissionFallback } from "../test-utils/ports.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { startGatewayClientWhenEventLoopReady } from "./client-start-readiness.js";
|
||||
import { GatewayClient, type GatewayClientOptions } from "./client.js";
|
||||
import type { EventFrame } from "./protocol/index.js";
|
||||
|
||||
// Aggregate docker live runs can contend on startup enough that the gateway
|
||||
// websocket handshake needs a wider budget than the single-provider reruns.
|
||||
@@ -292,6 +293,7 @@ export async function connectTestGatewayClient(params: {
|
||||
maxAttemptTimeoutMs?: number;
|
||||
clientDisplayName?: string | null;
|
||||
requestTimeoutMs?: number;
|
||||
onEvent?: (evt: EventFrame) => void;
|
||||
onRetry?: (attempt: number, error: Error) => void;
|
||||
}): Promise<GatewayClient> {
|
||||
const timeoutMs = params.timeoutMs ?? CLI_GATEWAY_CONNECT_TIMEOUT_MS;
|
||||
@@ -331,6 +333,7 @@ async function connectClientOnce(params: {
|
||||
deviceIdentity?: DeviceIdentity;
|
||||
clientDisplayName?: string | null;
|
||||
requestTimeoutMs?: number;
|
||||
onEvent?: (evt: EventFrame) => void;
|
||||
}): Promise<GatewayClient> {
|
||||
return await new Promise<GatewayClient>((resolve, reject) => {
|
||||
let done = false;
|
||||
@@ -367,6 +370,7 @@ async function connectClientOnce(params: {
|
||||
onHelloOk: () => finish({ client }),
|
||||
onConnectError: (error) => finish({ error }),
|
||||
onClose: failWithClose,
|
||||
onEvent: params.onEvent,
|
||||
};
|
||||
if (params.clientDisplayName !== null) {
|
||||
clientOptions.clientDisplayName = params.clientDisplayName ?? "vitest-live";
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from "./live-agent-probes.js";
|
||||
import { restoreLiveEnv, snapshotLiveEnv, type LiveEnvSnapshot } from "./live-env-test-helpers.js";
|
||||
import { renderSolidColorPngBase64 } from "./live-image-probe.js";
|
||||
import type { EventFrame } from "./protocol/index.js";
|
||||
|
||||
const LIVE = isLiveTestEnabled();
|
||||
const CODEX_HARNESS_LIVE = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CODEX_HARNESS);
|
||||
@@ -279,29 +280,31 @@ async function requestAgentText(params: {
|
||||
async function requestCodexCommandText(params: {
|
||||
client: GatewayClient;
|
||||
command: string;
|
||||
events: EventFrame[];
|
||||
expectedText: string | string[];
|
||||
isExpectedText?: (text: string) => boolean;
|
||||
sessionKey: string;
|
||||
}): Promise<string> {
|
||||
const { extractPayloadText } = await import("./test-helpers.agent-results.js");
|
||||
const payload = await params.client.request(
|
||||
"agent",
|
||||
const runId = `idem-${randomUUID()}-codex-command`;
|
||||
const started = await params.client.request(
|
||||
"chat.send",
|
||||
{
|
||||
sessionKey: params.sessionKey,
|
||||
idempotencyKey: `idem-${randomUUID()}-codex-command`,
|
||||
idempotencyKey: runId,
|
||||
message: params.command,
|
||||
deliver: false,
|
||||
thinking: "low",
|
||||
timeout: CODEX_HARNESS_AGENT_TIMEOUT_SECONDS,
|
||||
},
|
||||
{ expectFinal: true, timeoutMs: CODEX_HARNESS_REQUEST_TIMEOUT_MS },
|
||||
{ timeoutMs: CODEX_HARNESS_REQUEST_TIMEOUT_MS },
|
||||
);
|
||||
if (payload?.status !== "ok") {
|
||||
if (started?.status !== "started") {
|
||||
throw new Error(
|
||||
`codex command ${params.command} failed: status=${String(payload?.status)} payload=${JSON.stringify(payload)}`,
|
||||
`codex command ${params.command} did not start correctly: ${JSON.stringify(started)}`,
|
||||
);
|
||||
}
|
||||
const text = extractPayloadText(payload.result);
|
||||
const text = await waitForChatFinalText({
|
||||
events: params.events,
|
||||
runId,
|
||||
timeoutMs: CODEX_HARNESS_REQUEST_TIMEOUT_MS,
|
||||
});
|
||||
const expectedTexts = Array.isArray(params.expectedText)
|
||||
? params.expectedText
|
||||
: [params.expectedText];
|
||||
@@ -314,6 +317,54 @@ async function requestCodexCommandText(params: {
|
||||
return text;
|
||||
}
|
||||
|
||||
async function waitForChatFinalText(params: {
|
||||
events: EventFrame[];
|
||||
runId: string;
|
||||
timeoutMs: number;
|
||||
}): Promise<string> {
|
||||
const deadline = Date.now() + params.timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const text = params.events
|
||||
.map((event) => extractChatFinalText(event, params.runId))
|
||||
.find(Boolean);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
await delay(50);
|
||||
}
|
||||
throw new Error(`timed out waiting for chat final for ${params.runId}`);
|
||||
}
|
||||
|
||||
function extractChatFinalText(event: EventFrame, runId: string): string | undefined {
|
||||
if (event.event !== "chat") {
|
||||
return undefined;
|
||||
}
|
||||
const payload = event.payload;
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const record = payload as Record<string, unknown>;
|
||||
if (record.runId !== runId || record.state !== "final") {
|
||||
return undefined;
|
||||
}
|
||||
const message = record.message;
|
||||
if (!message || typeof message !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const messageRecord = message as Record<string, unknown>;
|
||||
if (typeof messageRecord.text === "string" && messageRecord.text.trim()) {
|
||||
return messageRecord.text;
|
||||
}
|
||||
const content = Array.isArray(messageRecord.content) ? messageRecord.content : [];
|
||||
return content
|
||||
.map((entry) =>
|
||||
entry && typeof entry === "object" ? (entry as Record<string, unknown>).text : undefined,
|
||||
)
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
||||
.join("\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function verifyCodexImageProbe(params: {
|
||||
client: GatewayClient;
|
||||
sessionKey: string;
|
||||
@@ -752,6 +803,7 @@ describeLive("gateway live (Codex harness)", () => {
|
||||
const deviceIdentity = await ensurePairedTestGatewayClientIdentity({
|
||||
displayName: "vitest-codex-harness-live",
|
||||
});
|
||||
const gatewayEvents: EventFrame[] = [];
|
||||
logCodexLiveStep("config-written", { configPath, modelKey, port });
|
||||
|
||||
const server = await startGatewayServer(port, {
|
||||
@@ -766,6 +818,9 @@ describeLive("gateway live (Codex harness)", () => {
|
||||
timeoutMs: GATEWAY_CONNECT_TIMEOUT_MS,
|
||||
requestTimeoutMs: CODEX_HARNESS_REQUEST_TIMEOUT_MS,
|
||||
clientDisplayName: "vitest-codex-harness-live",
|
||||
onEvent: (event) => {
|
||||
gatewayEvents.push(event);
|
||||
},
|
||||
});
|
||||
logCodexLiveStep("client-connected");
|
||||
|
||||
@@ -811,6 +866,7 @@ describeLive("gateway live (Codex harness)", () => {
|
||||
|
||||
const statusText = await requestCodexCommandText({
|
||||
client,
|
||||
events: gatewayEvents,
|
||||
sessionKey,
|
||||
command: "/codex status",
|
||||
expectedText: [...EXPECTED_CODEX_STATUS_COMMAND_TEXT],
|
||||
@@ -820,6 +876,7 @@ describeLive("gateway live (Codex harness)", () => {
|
||||
|
||||
const modelsText = await requestCodexCommandText({
|
||||
client,
|
||||
events: gatewayEvents,
|
||||
sessionKey,
|
||||
command: "/codex models",
|
||||
expectedText: [...EXPECTED_CODEX_MODELS_COMMAND_TEXT],
|
||||
|
||||
Reference in New Issue
Block a user