fix(acpx): avoid startup agent probes by default

This commit is contained in:
Peter Steinberger
2026-04-26 04:37:53 +01:00
parent ed1ac2fc44
commit 4edf22f63f
3 changed files with 44 additions and 8 deletions

View File

@@ -157,7 +157,10 @@ Then verify backend health:
### acpx command and version configuration
By default, the bundled `acpx` plugin uses its plugin-local pinned binary (`node_modules/.bin/acpx` inside the plugin package). Startup registers the backend as not-ready and a background job verifies `acpx --version`; if the binary is missing or mismatched, it runs `npm install --omit=dev --no-save acpx@<pinned>` and re-verifies. The gateway stays non-blocking throughout.
By default, the bundled `acpx` plugin registers the embedded ACP backend without
spawning an ACP agent during Gateway startup. Run `/acp doctor` for an explicit
live probe. Set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1` only when you need the
Gateway to probe the configured agent at startup.
Override the command or version in plugin config:
@@ -253,10 +256,11 @@ Restart the gateway after changing this value.
### Health probe agent configuration
The bundled `acpx` plugin probes one harness agent while deciding whether the
embedded runtime backend is ready. If `acp.allowedAgents` is set, it defaults to
the first allowed agent; otherwise it defaults to `codex`. If your deployment
needs a different ACP agent for health checks, set the probe agent explicitly:
When `/acp doctor` or the opt-in startup probe checks the backend, the bundled
`acpx` plugin probes one harness agent. If `acp.allowedAgents` is set, it
defaults to the first allowed agent; otherwise it defaults to `codex`. If your
deployment needs a different ACP agent for health checks, set the probe agent
explicitly:
```bash
openclaw config set plugins.entries.acpx.config.probeAgent claude

View File

@@ -47,6 +47,7 @@ async function makeTempDir(): Promise<string> {
afterEach(async () => {
runtimeRegistry.clear();
prepareAcpxCodexAuthConfigMock.mockClear();
delete process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE;
delete process.env.OPENCLAW_SKIP_ACPX_RUNTIME;
delete process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE;
for (const dir of tempDirs.splice(0)) {
@@ -99,7 +100,7 @@ describe("createAcpxRuntimeService", () => {
expect(getAcpRuntimeBackend("acpx")).toBeUndefined();
});
it("creates the embedded runtime state directory before probing", async () => {
it("creates the embedded runtime state directory without probing at startup by default", async () => {
const workspaceDir = await makeTempDir();
const stateDir = path.join(workspaceDir, "custom-state");
const ctx = createServiceContext(workspaceDir);
@@ -118,7 +119,30 @@ describe("createAcpxRuntimeService", () => {
await service.start(ctx);
await fs.access(stateDir);
expect(probeAvailability).not.toHaveBeenCalled();
expect(getAcpRuntimeBackend("acpx")?.healthy).toBeUndefined();
await service.stop?.(ctx);
});
it("can run the embedded runtime probe at startup when explicitly enabled", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const probeAvailability = vi.fn(async () => {});
const runtime = createMockRuntime({
probeAvailability,
isHealthy: () => true,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(probeAvailability).toHaveBeenCalledOnce();
expect(getAcpRuntimeBackend("acpx")?.healthy?.()).toBe(true);
await service.stop?.(ctx);
});
@@ -255,6 +279,7 @@ describe("createAcpxRuntimeService", () => {
});
it("can skip the embedded runtime probe via env", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -277,6 +302,7 @@ describe("createAcpxRuntimeService", () => {
});
it("formats non-string doctor details without losing object payloads", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime({

View File

@@ -31,6 +31,8 @@ type AcpxRuntimeLike = AcpRuntime & {
}>;
};
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
type AcpxRuntimeFactoryParams = {
pluginConfig: ResolvedAcpxPluginConfig;
logger?: PluginLogger;
@@ -128,6 +130,10 @@ function resolveAllowedAgentsProbeAgent(ctx: OpenClawPluginServiceContext): stri
return undefined;
}
function shouldRunStartupProbe(env: NodeJS.ProcessEnv = process.env): boolean {
return env[ENABLE_STARTUP_PROBE_ENV] === "1";
}
export function createAcpxRuntimeService(
params: CreateAcpxRuntimeServiceParams = {},
): OpenClawPluginService {
@@ -170,11 +176,11 @@ export function createAcpxRuntimeService(
registerAcpRuntimeBackend({
id: ACPX_BACKEND_ID,
runtime,
healthy: () => runtime?.isHealthy() ?? false,
...(shouldRunStartupProbe() ? { healthy: () => runtime?.isHealthy() ?? false } : {}),
});
ctx.logger.info(`embedded acpx runtime backend registered (cwd: ${pluginConfig.cwd})`);
if (process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE === "1") {
if (!shouldRunStartupProbe() || process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE === "1") {
return;
}