mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 08:41:23 +00:00
fix(ui): fix web UI after tsdown migration and typing changes
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveControlUiRootSync } from "../infra/control-ui-assets.js";
|
||||
import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
|
||||
import {
|
||||
buildControlUiAvatarUrl,
|
||||
@@ -17,34 +17,13 @@ export type ControlUiRequestOptions = {
|
||||
basePath?: string;
|
||||
config?: OpenClawConfig;
|
||||
agentId?: string;
|
||||
root?: ControlUiRootState;
|
||||
};
|
||||
|
||||
function resolveControlUiRoot(): string | null {
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const execDir = (() => {
|
||||
try {
|
||||
return path.dirname(fs.realpathSync(process.execPath));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const candidates = [
|
||||
// Packaged app: control-ui lives alongside the executable.
|
||||
execDir ? path.resolve(execDir, "control-ui") : null,
|
||||
// Running from dist: dist/gateway/control-ui.js -> dist/control-ui
|
||||
path.resolve(here, "../control-ui"),
|
||||
// Running from source: src/gateway/control-ui.ts -> dist/control-ui
|
||||
path.resolve(here, "../../dist/control-ui"),
|
||||
// Fallback to cwd (dev)
|
||||
path.resolve(process.cwd(), "dist", "control-ui"),
|
||||
].filter((dir): dir is string => Boolean(dir));
|
||||
for (const dir of candidates) {
|
||||
if (fs.existsSync(path.join(dir, "index.html"))) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export type ControlUiRootState =
|
||||
| { kind: "resolved"; path: string }
|
||||
| { kind: "invalid"; path: string }
|
||||
| { kind: "missing" };
|
||||
|
||||
function contentTypeForExt(ext: string): string {
|
||||
switch (ext) {
|
||||
@@ -288,7 +267,32 @@ export function handleControlUiHttpRequest(
|
||||
}
|
||||
}
|
||||
|
||||
const root = resolveControlUiRoot();
|
||||
const rootState = opts?.root;
|
||||
if (rootState?.kind === "invalid") {
|
||||
res.statusCode = 503;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.end(
|
||||
`Control UI assets not found at ${rootState.path}. Build them with \`pnpm ui:build\` (auto-installs UI deps), or update gateway.controlUi.root.`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (rootState?.kind === "missing") {
|
||||
res.statusCode = 503;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.end(
|
||||
"Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const root =
|
||||
rootState?.kind === "resolved"
|
||||
? rootState.path
|
||||
: resolveControlUiRootSync({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
if (!root) {
|
||||
res.statusCode = 503;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
@@ -13,7 +13,11 @@ import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
||||
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
||||
import { handleControlUiAvatarRequest, handleControlUiHttpRequest } from "./control-ui.js";
|
||||
import {
|
||||
handleControlUiAvatarRequest,
|
||||
handleControlUiHttpRequest,
|
||||
type ControlUiRootState,
|
||||
} from "./control-ui.js";
|
||||
import { applyHookMappings } from "./hooks-mapping.js";
|
||||
import {
|
||||
extractHookToken,
|
||||
@@ -206,6 +210,7 @@ export function createGatewayHttpServer(opts: {
|
||||
canvasHost: CanvasHostHandler | null;
|
||||
controlUiEnabled: boolean;
|
||||
controlUiBasePath: string;
|
||||
controlUiRoot?: ControlUiRootState;
|
||||
openAiChatCompletionsEnabled: boolean;
|
||||
openResponsesEnabled: boolean;
|
||||
openResponsesConfig?: import("../config/types.gateway.js").GatewayHttpResponsesConfig;
|
||||
@@ -218,6 +223,7 @@ export function createGatewayHttpServer(opts: {
|
||||
canvasHost,
|
||||
controlUiEnabled,
|
||||
controlUiBasePath,
|
||||
controlUiRoot,
|
||||
openAiChatCompletionsEnabled,
|
||||
openResponsesEnabled,
|
||||
openResponsesConfig,
|
||||
@@ -301,6 +307,7 @@ export function createGatewayHttpServer(opts: {
|
||||
handleControlUiHttpRequest(req, res, {
|
||||
basePath: controlUiBasePath,
|
||||
config: configSnapshot,
|
||||
root: controlUiRoot,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
|
||||
@@ -20,6 +20,7 @@ export type GatewayRuntimeConfig = {
|
||||
openResponsesEnabled: boolean;
|
||||
openResponsesConfig?: import("../config/types.gateway.js").GatewayHttpResponsesConfig;
|
||||
controlUiBasePath: string;
|
||||
controlUiRoot?: string;
|
||||
resolvedAuth: ResolvedGatewayAuth;
|
||||
authMode: ResolvedGatewayAuth["mode"];
|
||||
tailscaleConfig: GatewayTailscaleConfig;
|
||||
@@ -51,6 +52,11 @@ export async function resolveGatewayRuntimeConfig(params: {
|
||||
const openResponsesConfig = params.cfg.gateway?.http?.endpoints?.responses;
|
||||
const openResponsesEnabled = params.openResponsesEnabled ?? openResponsesConfig?.enabled ?? false;
|
||||
const controlUiBasePath = normalizeControlUiBasePath(params.cfg.gateway?.controlUi?.basePath);
|
||||
const controlUiRootRaw = params.cfg.gateway?.controlUi?.root;
|
||||
const controlUiRoot =
|
||||
typeof controlUiRootRaw === "string" && controlUiRootRaw.trim().length > 0
|
||||
? controlUiRootRaw.trim()
|
||||
: undefined;
|
||||
const authBase = params.cfg.gateway?.auth ?? {};
|
||||
const authOverrides = params.auth ?? {};
|
||||
const authConfig = {
|
||||
@@ -103,6 +109,7 @@ export async function resolveGatewayRuntimeConfig(params: {
|
||||
? { ...openResponsesConfig, enabled: openResponsesEnabled }
|
||||
: undefined,
|
||||
controlUiBasePath,
|
||||
controlUiRoot,
|
||||
resolvedAuth,
|
||||
authMode,
|
||||
tailscaleConfig,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { PluginRegistry } from "../plugins/registry.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ResolvedGatewayAuth } from "./auth.js";
|
||||
import type { ChatAbortControllerEntry } from "./chat-abort.js";
|
||||
import type { ControlUiRootState } from "./control-ui.js";
|
||||
import type { HooksConfigResolved } from "./hooks.js";
|
||||
import type { DedupeEntry } from "./server-shared.js";
|
||||
import type { GatewayTlsRuntime } from "./server/tls.js";
|
||||
@@ -27,6 +28,7 @@ export async function createGatewayRuntimeState(params: {
|
||||
port: number;
|
||||
controlUiEnabled: boolean;
|
||||
controlUiBasePath: string;
|
||||
controlUiRoot?: ControlUiRootState;
|
||||
openAiChatCompletionsEnabled: boolean;
|
||||
openResponsesEnabled: boolean;
|
||||
openResponsesConfig?: import("../config/types.gateway.js").GatewayHttpResponsesConfig;
|
||||
@@ -112,6 +114,7 @@ export async function createGatewayRuntimeState(params: {
|
||||
canvasHost,
|
||||
controlUiEnabled: params.controlUiEnabled,
|
||||
controlUiBasePath: params.controlUiBasePath,
|
||||
controlUiRoot: params.controlUiRoot,
|
||||
openAiChatCompletionsEnabled: params.openAiChatCompletionsEnabled,
|
||||
openResponsesEnabled: params.openResponsesEnabled,
|
||||
openResponsesConfig: params.openResponsesConfig,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import path from "node:path";
|
||||
import type { CanvasHostServer } from "../canvas-host/server.js";
|
||||
import type { PluginServicesHandle } from "../plugins/services.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ControlUiRootState } from "./control-ui.js";
|
||||
import type { startBrowserControlServerIfEnabled } from "./server-browser.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { registerSkillsChangeListener } from "../agents/skills/refresh.js";
|
||||
@@ -18,6 +20,11 @@ import {
|
||||
} from "../config/config.js";
|
||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||
import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js";
|
||||
import {
|
||||
ensureControlUiAssetsBuilt,
|
||||
resolveControlUiRootOverrideSync,
|
||||
resolveControlUiRootSync,
|
||||
} from "../infra/control-ui-assets.js";
|
||||
import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
|
||||
import { logAcceptedEnvOption } from "../infra/env.js";
|
||||
import { createExecApprovalForwarder } from "../infra/exec-approval-forwarder.js";
|
||||
@@ -87,6 +94,7 @@ const logReload = log.child("reload");
|
||||
const logHooks = log.child("hooks");
|
||||
const logPlugins = log.child("plugins");
|
||||
const logWsControl = log.child("ws");
|
||||
const gatewayRuntime = runtimeForLogger(log);
|
||||
const canvasRuntime = runtimeForLogger(logCanvas);
|
||||
|
||||
export type GatewayServer = {
|
||||
@@ -253,6 +261,7 @@ export async function startGatewayServer(
|
||||
openResponsesEnabled,
|
||||
openResponsesConfig,
|
||||
controlUiBasePath,
|
||||
controlUiRoot: controlUiRootOverride,
|
||||
resolvedAuth,
|
||||
tailscaleConfig,
|
||||
tailscaleMode,
|
||||
@@ -260,6 +269,38 @@ export async function startGatewayServer(
|
||||
let hooksConfig = runtimeConfig.hooksConfig;
|
||||
const canvasHostEnabled = runtimeConfig.canvasHostEnabled;
|
||||
|
||||
let controlUiRootState: ControlUiRootState | undefined;
|
||||
if (controlUiRootOverride) {
|
||||
const resolvedOverride = resolveControlUiRootOverrideSync(controlUiRootOverride);
|
||||
const resolvedOverridePath = path.resolve(controlUiRootOverride);
|
||||
controlUiRootState = resolvedOverride
|
||||
? { kind: "resolved", path: resolvedOverride }
|
||||
: { kind: "invalid", path: resolvedOverridePath };
|
||||
if (!resolvedOverride) {
|
||||
log.warn(`gateway: controlUi.root not found at ${resolvedOverridePath}`);
|
||||
}
|
||||
} else if (controlUiEnabled) {
|
||||
let resolvedRoot = resolveControlUiRootSync({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
if (!resolvedRoot) {
|
||||
const ensureResult = await ensureControlUiAssetsBuilt(gatewayRuntime);
|
||||
if (!ensureResult.ok && ensureResult.message) {
|
||||
log.warn(`gateway: ${ensureResult.message}`);
|
||||
}
|
||||
resolvedRoot = resolveControlUiRootSync({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
}
|
||||
controlUiRootState = resolvedRoot
|
||||
? { kind: "resolved", path: resolvedRoot }
|
||||
: { kind: "missing" };
|
||||
}
|
||||
|
||||
const wizardRunner = opts.wizardRunner ?? runOnboardingWizard;
|
||||
const { wizardSessions, findRunningWizard, purgeWizardSession } = createWizardSessionTracker();
|
||||
|
||||
@@ -291,6 +332,7 @@ export async function startGatewayServer(
|
||||
port,
|
||||
controlUiEnabled,
|
||||
controlUiBasePath,
|
||||
controlUiRoot: controlUiRootState,
|
||||
openAiChatCompletionsEnabled,
|
||||
openResponsesEnabled,
|
||||
openResponsesConfig,
|
||||
|
||||
Reference in New Issue
Block a user