fix: stage ACP and Codex runtime deps

This commit is contained in:
Peter Steinberger
2026-04-21 08:46:14 +01:00
parent 6a4a60fe25
commit 047acaa176
16 changed files with 162 additions and 5 deletions

View File

@@ -6,7 +6,7 @@ import path from "node:path";
import { describe, expect, it } from "vitest";
import { getAcpRuntimeBackend } from "../acp/runtime/registry.js";
import { isLiveTestEnabled } from "../agents/live-test-helpers.js";
import { clearRuntimeConfigSnapshot, loadConfig } from "../config/config.js";
import { clearConfigCache, clearRuntimeConfigSnapshot, loadConfig } from "../config/config.js";
import { isTruthyEnvValue } from "../infra/env.js";
import {
pinActivePluginChannelRegistry,
@@ -213,18 +213,34 @@ async function bindConversationAndWait(params: {
originatingAccountId: string;
timeoutMs?: number;
}): Promise<{ mainAssistantTexts: string[]; spawnedSessionKey: string }> {
const timeoutMs = params.timeoutMs ?? 90_000;
const timeoutMs = params.timeoutMs ?? LIVE_TIMEOUT_MS;
const startedAt = Date.now();
let attempt = 0;
while (Date.now() - startedAt < timeoutMs) {
attempt += 1;
const backend = getAcpRuntimeBackend("acpx");
const runtime = backend?.runtime as { probeAvailability?: () => Promise<void> } | undefined;
const runtime = backend?.runtime as
| {
probeAvailability?: () => Promise<void>;
doctor?: () => Promise<{ message?: string; details?: string[] }>;
}
| undefined;
if (runtime?.probeAvailability) {
await runtime.probeAvailability().catch(() => {});
}
if (!(backend?.healthy?.() ?? false)) {
if (runtime?.doctor && (attempt === 1 || attempt % 6 === 0)) {
const report = await runtime.doctor().catch((error) => ({
message: error instanceof Error ? error.message : String(error),
details: [],
}));
logLiveStep(
`acpx doctor before bind attempt ${attempt}: ${report.message ?? "unknown"}${
report.details?.length ? ` (${report.details.join("; ")})` : ""
}`,
);
}
logLiveStep(`acpx backend still unhealthy before bind attempt ${attempt}`);
await sleep(5_000);
continue;
@@ -451,6 +467,8 @@ describeLive("gateway live (ACP bind)", () => {
},
plugins: {
...cfg.plugins,
enabled: true,
allow: Array.from(new Set([...(cfg.plugins?.allow ?? []), "acpx"])),
entries: {
...cfg.plugins?.entries,
acpx: {
@@ -458,6 +476,7 @@ describeLive("gateway live (ACP bind)", () => {
enabled: true,
config: {
...acpxEntry?.config,
probeAgent: liveAgent,
permissionMode: "approve-all",
nonInteractivePermissions: "deny",
...(agentCommandOverride
@@ -482,12 +501,15 @@ describeLive("gateway live (ACP bind)", () => {
};
await fs.writeFile(tempConfigPath, `${JSON.stringify(nextCfg, null, 2)}\n`);
process.env.OPENCLAW_CONFIG_PATH = tempConfigPath;
clearConfigCache();
clearRuntimeConfigSnapshot();
logLiveStep(`starting gateway on port ${String(port)}`);
const server = await startGatewayServer(port, {
bind: "loopback",
auth: { mode: "token", token },
controlUiEnabled: false,
awaitStartupSidecars: true,
});
logLiveStep("gateway startup returned");
await waitForGatewayPort({ host: "127.0.0.1", port, timeoutMs: CONNECT_TIMEOUT_MS });
@@ -781,6 +803,7 @@ describeLive("gateway live (ACP bind)", () => {
logLiveStep("bound session created cron via MCP and CLI verification passed");
} finally {
releasePinnedPluginChannelRegistry(channelRegistry);
clearConfigCache();
clearRuntimeConfigSnapshot();
await client.stopAndWait({ timeoutMs: 2_000 }).catch(() => {});
await server.close();

View File

@@ -392,6 +392,7 @@ export async function startGatewayPostAttachRuntime(
onPluginServices?: (pluginServices: PluginServicesHandle | null) => void;
onSidecarsReady?: () => void;
startupTrace?: GatewayStartupTrace;
awaitSidecars?: boolean;
},
runtimeDeps: GatewayPostAttachRuntimeDeps = defaultGatewayPostAttachRuntimeDeps,
) {
@@ -483,6 +484,19 @@ export async function startGatewayPostAttachRuntime(
params.log.warn(`gateway sidecars failed to start: ${String(err)}`);
});
if (params.awaitSidecars === true) {
const [stopGatewayUpdateCheck, tailscaleCleanup, sidecarsResult] = await Promise.all([
stopGatewayUpdateCheckPromise,
tailscaleCleanupPromise,
sidecarsPromise,
]);
return {
stopGatewayUpdateCheck,
tailscaleCleanup,
pluginServices: sidecarsResult.pluginServices,
};
}
const [stopGatewayUpdateCheck, tailscaleCleanup] = await Promise.all([
stopGatewayUpdateCheckPromise,
tailscaleCleanupPromise,

View File

@@ -227,6 +227,10 @@ export type GatewayServerOptions = {
runtime: import("../runtime.js").RuntimeEnv,
prompter: import("../wizard/prompts.js").WizardPrompter,
) => Promise<void>;
/**
* Test-only: wait for post-listen sidecars such as plugin services before returning.
*/
awaitStartupSidecars?: boolean;
/**
* Optional startup timestamp used for concise readiness logging.
*/
@@ -833,6 +837,7 @@ export async function startGatewayServer(
startupSidecarsReady = true;
},
startupTrace,
awaitSidecars: opts.awaitStartupSidecars,
}),
));
startupTrace.mark("ready");