fix(gateway): log canvas host mount after bind

This commit is contained in:
Peter Steinberger
2026-05-04 09:04:46 +01:00
parent ef79347763
commit 03ad3c0684
4 changed files with 48 additions and 4 deletions

View File

@@ -65,6 +65,7 @@ Docs: https://docs.openclaw.ai
- Control UI/Talk: retry from a failed realtime Talk session on the next Talk click instead of requiring a separate stale-session stop click first. Thanks @vincentkoc.
- Google Chat: create an isolated Google auth transport per auth client, so google-auth-library interceptor mutations do not accumulate across webhook verification and access-token clients. Thanks @vincentkoc.
- Control UI/performance: cap long-task and long-animation-frame diagnostics in the shared event log, so slow-render telemetry does not evict gateway/plugin events from the Debug and Overview views. Thanks @vincentkoc.
- Gateway/startup: log the canvas host mount only after the HTTP server has bound, so startup logs no longer report the canvas host as mounted before it can serve requests.
- Web fetch: late-bind `web_fetch` config and provider fallback metadata from the active runtime snapshot, matching `web_search` so long-lived tools do not use stale fetch provider settings. Thanks @vincentkoc.
- Discord: clear stale startup probe bot/application status when the async bot probe throws, not just when it returns a degraded probe result. Thanks @vincentkoc.
- Web search: scope explicit bundled `web_search` provider runtime loading through manifest ownership, so selecting DuckDuckGo/Gemini/etc. does not import unrelated bundled providers or log their optional dependency failures. Thanks @vincentkoc.

View File

@@ -1,4 +1,7 @@
import { afterEach, describe, expect, it } from "vitest";
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import {
getActivePluginChannelRegistry,
@@ -26,10 +29,13 @@ function createRegistryWithRoute(path: string) {
}
describe("createGatewayRuntimeState", () => {
const tempDirs: string[] = [];
afterEach(() => {
releasePinnedPluginHttpRouteRegistry();
releasePinnedPluginChannelRegistry();
resetPluginRuntimeStateForTest();
return Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
it("releases post-bootstrap repinned plugin registries on cleanup", async () => {
@@ -70,4 +76,38 @@ describe("createGatewayRuntimeState", () => {
expect(resolveActivePluginHttpRouteRegistry(fallbackRegistry)).toBe(startupRegistry);
expect(getActivePluginChannelRegistry()).toBe(startupRegistry);
});
it("creates the canvas host without logging it before HTTP bind", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "openclaw-canvas-runtime-"));
tempDirs.push(root);
const registry = createEmptyPluginRegistry();
const logCanvas = { info: vi.fn(), warn: vi.fn() };
const runtimeState = await createGatewayRuntimeState({
cfg: { canvasHost: { root, liveReload: false } },
bindHost: "127.0.0.1",
port: 18789,
controlUiEnabled: false,
controlUiBasePath: "/",
openAiChatCompletionsEnabled: false,
openResponsesEnabled: false,
resolvedAuth: {} as never,
getResolvedAuth: () => ({}) as never,
hooksConfig: () => null,
getHookClientIpConfig: () => ({}) as never,
pluginRegistry: registry,
deps: {} as never,
canvasRuntime: { log: () => {} } as never,
canvasHostEnabled: true,
allowCanvasHostInTests: true,
logCanvas,
log: { info: () => {}, warn: () => {} },
logHooks: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} } as never,
logPlugins: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} } as never,
});
expect(runtimeState.canvasHost?.rootDir).toBe(root);
expect(logCanvas.info).not.toHaveBeenCalled();
await runtimeState.canvasHost?.close();
});
});

View File

@@ -130,9 +130,6 @@ export async function createGatewayRuntimeState(params: {
});
if (handler.rootDir) {
canvasHost = handler;
params.logCanvas.info(
`canvas host mounted at http://${params.bindHost}:${params.port}${CANVAS_HOST_PATH}/ (root ${handler.rootDir})`,
);
}
} catch (err) {
params.logCanvas.warn(`canvas host failed to start: ${String(err)}`);

View File

@@ -1,6 +1,7 @@
import { monitorEventLoopDelay, performance } from "node:perf_hooks";
import { getActiveEmbeddedRunCount } from "../agents/pi-embedded-runner/run-state.js";
import { getTotalPendingReplies } from "../auto-reply/reply/dispatcher-registry.js";
import { CANVAS_HOST_PATH } from "../canvas-host/a2ui-shared.js";
import type { CanvasHostServer } from "../canvas-host/server.js";
import type { ChannelRuntimeSurface } from "../channels/plugins/channel-runtime-surface.types.js";
import {
@@ -1354,6 +1355,11 @@ export async function startGatewayServer(
context: gatewayRequestContext,
});
await startListening();
if (canvasHost?.rootDir) {
logCanvas.info(
`canvas host mounted at http://${bindHost}:${port}${CANVAS_HOST_PATH}/ (root ${canvasHost.rootDir})`,
);
}
startupTrace.mark("http.bound");
const sessionDeliveryRecoveryMaxEnqueuedAt = Date.now();
let postAttachRuntimeReturned = false;