fix(release): stabilize release validation probes

This commit is contained in:
Peter Steinberger
2026-04-27 08:17:47 +01:00
parent 3200378ab4
commit 720ea766e6
6 changed files with 83 additions and 12 deletions

View File

@@ -61,6 +61,8 @@ const OMITTED_QA_EXTENSION_PREFIXES = [
export const CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS = 120_000;
export const CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS = 10_000;
export const CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS = 30_000;
export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS =
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000;
export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_000;
export const CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
@@ -1635,13 +1637,12 @@ async function resolveInstalledGatewayStatusArgs(params) {
return [
"gateway",
"status",
"--deep",
"--require-rpc",
"--timeout",
String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS),
];
}
return ["gateway", "status", "--deep"];
return ["gateway", "status"];
}
export async function canConnectToLoopbackPort(port, timeoutMs = 1_000) {
@@ -1684,7 +1685,7 @@ async function waitForInstalledGateway(params) {
cwd: params.lane.homeDir,
env: params.env,
logPath: params.logPath,
timeoutMs: 20_000,
timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
check: false,
});
if (result.exitCode === 0) {
@@ -1711,7 +1712,7 @@ async function waitForInstalledGatewayToStop(params) {
cwd: params.lane.homeDir,
env: params.env,
logPath: params.logPath,
timeoutMs: 20_000,
timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
check: false,
});
const portReachable = await canConnectToLoopbackPort(params.lane.gatewayPort);
@@ -2364,7 +2365,7 @@ async function waitForGateway(params) {
env: params.env,
args: statusArgs,
logPath: params.logPath,
timeoutMs: 20_000,
timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
check: false,
});
} catch {
@@ -2398,13 +2399,12 @@ async function resolveGatewayStatusArgs(lane, env, logPath) {
return [
"gateway",
"status",
"--deep",
"--require-rpc",
"--timeout",
String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS),
];
}
return ["gateway", "status", "--deep"];
return ["gateway", "status"];
}
async function runModelsSet(params) {

View File

@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import {
runAgentHarnessAgentEndHook,
runAgentHarnessBeforeAgentFinalizeHook,
runAgentHarnessLlmInputHook,
runAgentHarnessLlmOutputHook,
} from "./lifecycle-hook-helpers.js";
const legacyHookRunner = {
hasHooks: () => true,
};
describe("agent harness lifecycle hook helpers", () => {
it("ignores legacy hook runners that advertise llm_input without a runner method", () => {
expect(() =>
runAgentHarnessLlmInputHook({
ctx: {},
event: {},
hookRunner: legacyHookRunner,
} as never),
).not.toThrow();
});
it("ignores legacy hook runners that advertise llm_output without a runner method", () => {
expect(() =>
runAgentHarnessLlmOutputHook({
ctx: {},
event: {},
hookRunner: legacyHookRunner,
} as never),
).not.toThrow();
});
it("ignores legacy hook runners that advertise agent_end without a runner method", () => {
expect(() =>
runAgentHarnessAgentEndHook({
ctx: {},
event: {},
hookRunner: legacyHookRunner,
} as never),
).not.toThrow();
});
it("continues when legacy hook runners advertise before_agent_finalize without a runner method", async () => {
await expect(
runAgentHarnessBeforeAgentFinalizeHook({
ctx: {},
event: {},
hookRunner: legacyHookRunner,
} as never),
).resolves.toEqual({ action: "continue" });
});
});

View File

@@ -19,7 +19,7 @@ export function runAgentHarnessLlmInputHook(params: {
hookRunner?: AgentHarnessHookRunner;
}): void {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("llm_input")) {
if (!hookRunner?.hasHooks("llm_input") || typeof hookRunner.runLlmInput !== "function") {
return;
}
void hookRunner.runLlmInput(params.event, buildAgentHookContext(params.ctx)).catch((error) => {
@@ -33,7 +33,7 @@ export function runAgentHarnessLlmOutputHook(params: {
hookRunner?: AgentHarnessHookRunner;
}): void {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("llm_output")) {
if (!hookRunner?.hasHooks("llm_output") || typeof hookRunner.runLlmOutput !== "function") {
return;
}
void hookRunner.runLlmOutput(params.event, buildAgentHookContext(params.ctx)).catch((error) => {
@@ -47,7 +47,7 @@ export function runAgentHarnessAgentEndHook(params: {
hookRunner?: AgentHarnessHookRunner;
}): void {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("agent_end")) {
if (!hookRunner?.hasHooks("agent_end") || typeof hookRunner.runAgentEnd !== "function") {
return;
}
void hookRunner.runAgentEnd(params.event, buildAgentHookContext(params.ctx)).catch((error) => {
@@ -66,7 +66,10 @@ export async function runAgentHarnessBeforeAgentFinalizeHook(params: {
hookRunner?: AgentHarnessHookRunner;
}): Promise<AgentHarnessBeforeAgentFinalizeOutcome> {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("before_agent_finalize")) {
if (
!hookRunner?.hasHooks("before_agent_finalize") ||
typeof hookRunner.runBeforeAgentFinalize !== "function"
) {
return { action: "continue" };
}
try {

View File

@@ -17,6 +17,16 @@ describe("gateway codex harness live helpers", () => {
expect(isExpectedCodexStatusCommandText(text)).toBe(true);
});
it("accepts current status prose that reports session context without the session id", () => {
const text = [
"OpenClaw is running on `openai/gpt-5.5` with low reasoning/text settings.",
"",
"Session context is light: `22k/272k` tokens used, `8%`, no compactions. There is 1 active task: `/codex status`.",
].join("\n");
expect(isExpectedCodexStatusCommandText(text)).toBe(true);
});
it("rejects status prose for a different codex session", () => {
const text =
"OpenClaw is running on `openai/gpt-5.5` with low reasoning/text settings. Context is at `22k/272k` tokens, no compactions, and the current session is `agent:dev:other`.";

View File

@@ -94,7 +94,8 @@ export function isExpectedCodexStatusCommandText(text: string): boolean {
normalized.includes("session: agent:dev:live-codex-harness") ||
normalized.includes("session `agent:dev:live-codex-harness`") ||
normalized.includes("current session is `agent:dev:live-codex-harness`") ||
normalized.includes("current session is agent:dev:live-codex-harness");
normalized.includes("current session is agent:dev:live-codex-harness") ||
(normalized.includes("session context") && normalized.includes("active task: `/codex status`"));
const mentionsModel =
normalized.includes("`openai/") ||
normalized.includes(" openai/") ||

View File

@@ -13,6 +13,7 @@ import {
buildDiscordSmokeGuildsConfig,
buildRealUpdateEnv,
CROSS_OS_GATEWAY_READY_TIMEOUT_MS,
CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS,
CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS,
CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS,
@@ -51,6 +52,9 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
it("keeps gateway RPC status probes patient enough for live release startup", () => {
expect(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000);
expect(CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS).toBeGreaterThan(
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS,
);
expect(CROSS_OS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(180_000);
expect(CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(300_000);
});