mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:30:42 +00:00
perf(cli): trim gateway status startup work
This commit is contained in:
@@ -55,7 +55,14 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [
|
||||
{
|
||||
commandPath: ["gateway", "status"],
|
||||
exact: true,
|
||||
policy: { routeConfigGuard: "always" },
|
||||
policy: {
|
||||
routeConfigGuard: "always",
|
||||
// `gateway status` is a built-in daemon/RPC health path. Loading the
|
||||
// full plugin registry here eagerly scans and validates every channel
|
||||
// plugin before the command can even connect to the already-running
|
||||
// gateway, which makes this frequently-used status check painfully slow.
|
||||
loadPlugins: "never",
|
||||
},
|
||||
route: { id: "gateway-status" },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,32 +42,8 @@ export async function probeGatewayStatus(opts: {
|
||||
enabled: opts.json !== true,
|
||||
},
|
||||
async () => {
|
||||
if (opts.requireRpc) {
|
||||
const { callGateway } = await import("../../gateway/call.js");
|
||||
await callGateway({
|
||||
url: opts.url,
|
||||
token: opts.token,
|
||||
password: opts.password,
|
||||
tlsFingerprint: opts.tlsFingerprint,
|
||||
method: "status",
|
||||
timeoutMs: opts.timeoutMs,
|
||||
...(opts.configPath ? { configPath: opts.configPath } : {}),
|
||||
});
|
||||
const { probeGateway } = await loadProbeGatewayModule();
|
||||
const authProbe = await probeGateway({
|
||||
url: opts.url,
|
||||
auth: {
|
||||
token: opts.token,
|
||||
password: opts.password,
|
||||
},
|
||||
tlsFingerprint: opts.tlsFingerprint,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
includeDetails: false,
|
||||
}).catch(() => null);
|
||||
return { ok: true as const, authProbe };
|
||||
}
|
||||
const { probeGateway } = await loadProbeGatewayModule();
|
||||
return await probeGateway({
|
||||
const probe = await probeGateway({
|
||||
url: opts.url,
|
||||
auth: {
|
||||
token: opts.token,
|
||||
@@ -75,12 +51,13 @@ export async function probeGatewayStatus(opts: {
|
||||
},
|
||||
tlsFingerprint: opts.tlsFingerprint,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
includeDetails: false,
|
||||
includeDetails: opts.requireRpc === true,
|
||||
detailLevel: opts.requireRpc === true ? "full" : "none",
|
||||
});
|
||||
return probe;
|
||||
},
|
||||
);
|
||||
const auth =
|
||||
"auth" in result ? result.auth : "authProbe" in result ? result.authProbe?.auth : undefined;
|
||||
const auth = result.auth;
|
||||
if (result.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -89,8 +66,8 @@ export async function probeGatewayStatus(opts: {
|
||||
kind === "read"
|
||||
? auth?.capability && auth.capability !== "unknown"
|
||||
? auth.capability
|
||||
: // The status RPC proves read access even when a follow-up hello probe
|
||||
// cannot recover richer scope metadata.
|
||||
: // A successful detailed probe performs read RPCs, so it proves read access
|
||||
// even when hello metadata cannot recover richer scope metadata.
|
||||
"read_only"
|
||||
: auth?.capability,
|
||||
auth,
|
||||
|
||||
@@ -207,6 +207,7 @@ describe("gatherDaemonStatus", () => {
|
||||
expect(status.gateway?.probeUrl).toBe("wss://127.0.0.1:19001");
|
||||
expect(status.rpc?.url).toBe("wss://127.0.0.1:19001");
|
||||
expect(status.rpc?.ok).toBe(true);
|
||||
expect(inspectGatewayRestart).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("forwards requireRpc and configPath to the daemon probe", async () => {
|
||||
@@ -542,7 +543,12 @@ describe("gatherDaemonStatus", () => {
|
||||
expect(status.rpc).toBeUndefined();
|
||||
});
|
||||
|
||||
it("surfaces stale gateway listener pids from restart health inspection", async () => {
|
||||
it("surfaces stale gateway listener pids from restart health inspection when probe fails", async () => {
|
||||
callGatewayStatusProbe.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
url: "ws://127.0.0.1:19001",
|
||||
error: "timeout",
|
||||
});
|
||||
inspectGatewayRestart.mockResolvedValueOnce({
|
||||
runtime: { status: "running", pid: 8000 },
|
||||
portUsage: {
|
||||
|
||||
@@ -207,13 +207,18 @@ async function loadDaemonConfigContext(
|
||||
resolveStateDir(mergedDaemonEnv as NodeJS.ProcessEnv),
|
||||
);
|
||||
|
||||
const cliIO = createConfigIO({ env: process.env, configPath: cliConfigPath });
|
||||
const cliIO = createConfigIO({
|
||||
env: process.env,
|
||||
configPath: cliConfigPath,
|
||||
pluginValidation: "skip",
|
||||
});
|
||||
const sharesDaemonConfigContext = !serviceEnv && cliConfigPath === daemonConfigPath;
|
||||
const daemonIO = sharesDaemonConfigContext
|
||||
? cliIO
|
||||
: createConfigIO({
|
||||
env: mergedDaemonEnv,
|
||||
configPath: daemonConfigPath,
|
||||
pluginValidation: "skip",
|
||||
});
|
||||
|
||||
const cliSnapshotPromise = cliIO.readConfigFileSnapshot().catch(() => null);
|
||||
@@ -444,7 +449,7 @@ export async function gatherDaemonStatus(
|
||||
rpcAuthWarning = undefined;
|
||||
}
|
||||
const health =
|
||||
opts.probe && loaded
|
||||
opts.probe && loaded && rpc?.ok !== true
|
||||
? await loadRestartHealthModule()
|
||||
.then(({ inspectGatewayRestart }) =>
|
||||
inspectGatewayRestart({
|
||||
|
||||
@@ -1144,7 +1144,9 @@ async function finalizeReadConfigSnapshotInternalResult(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
export function createConfigIO(
|
||||
overrides: ConfigIoDeps & { pluginValidation?: "full" | "skip" } = {},
|
||||
) {
|
||||
const deps = normalizeDeps(overrides);
|
||||
const configPath = resolveConfigPathForDeps(deps);
|
||||
|
||||
@@ -1260,7 +1262,10 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
if (preValidationDuplicates.length > 0) {
|
||||
throw new DuplicateAgentDirError(preValidationDuplicates);
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, { env: deps.env });
|
||||
const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, {
|
||||
env: deps.env,
|
||||
pluginValidation: overrides.pluginValidation,
|
||||
});
|
||||
if (!validated.ok) {
|
||||
observeLoadConfigSnapshot({
|
||||
...createConfigFileSnapshot({
|
||||
@@ -1436,7 +1441,10 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
const legacyResolution = resolveLegacyConfigForRead(resolvedConfigRaw, effectiveParsed);
|
||||
const effectiveConfigRaw = legacyResolution.effectiveConfigRaw;
|
||||
fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
|
||||
const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, { env: deps.env });
|
||||
const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, {
|
||||
env: deps.env,
|
||||
pluginValidation: overrides.pluginValidation,
|
||||
});
|
||||
if (!validated.ok) {
|
||||
return await finalizeReadConfigSnapshotInternalResult(deps, {
|
||||
snapshot: createConfigFileSnapshot({
|
||||
|
||||
@@ -708,21 +708,29 @@ type ValidateConfigWithPluginsResult =
|
||||
|
||||
export function validateConfigObjectWithPlugins(
|
||||
raw: unknown,
|
||||
params?: { env?: NodeJS.ProcessEnv },
|
||||
params?: { env?: NodeJS.ProcessEnv; pluginValidation?: "full" | "skip" },
|
||||
): ValidateConfigWithPluginsResult {
|
||||
return validateConfigObjectWithPluginsBase(raw, { applyDefaults: true, env: params?.env });
|
||||
return validateConfigObjectWithPluginsBase(raw, {
|
||||
applyDefaults: true,
|
||||
env: params?.env,
|
||||
pluginValidation: params?.pluginValidation ?? "full",
|
||||
});
|
||||
}
|
||||
|
||||
export function validateConfigObjectRawWithPlugins(
|
||||
raw: unknown,
|
||||
params?: { env?: NodeJS.ProcessEnv },
|
||||
params?: { env?: NodeJS.ProcessEnv; pluginValidation?: "full" | "skip" },
|
||||
): ValidateConfigWithPluginsResult {
|
||||
return validateConfigObjectWithPluginsBase(raw, { applyDefaults: false, env: params?.env });
|
||||
return validateConfigObjectWithPluginsBase(raw, {
|
||||
applyDefaults: false,
|
||||
env: params?.env,
|
||||
pluginValidation: params?.pluginValidation ?? "full",
|
||||
});
|
||||
}
|
||||
|
||||
function validateConfigObjectWithPluginsBase(
|
||||
raw: unknown,
|
||||
opts: { applyDefaults: boolean; env?: NodeJS.ProcessEnv },
|
||||
opts: { applyDefaults: boolean; env?: NodeJS.ProcessEnv; pluginValidation?: "full" | "skip" },
|
||||
): ValidateConfigWithPluginsResult {
|
||||
const base = opts.applyDefaults ? validateConfigObject(raw) : validateConfigObjectRaw(raw);
|
||||
if (!base.ok) {
|
||||
@@ -730,6 +738,14 @@ function validateConfigObjectWithPluginsBase(
|
||||
}
|
||||
|
||||
const config = base.config;
|
||||
if (opts.pluginValidation === "skip") {
|
||||
return {
|
||||
ok: true,
|
||||
config,
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const warnings: ConfigValidationIssue[] = [];
|
||||
const hasExplicitPluginsConfig =
|
||||
|
||||
Reference in New Issue
Block a user