mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-09 07:02:55 +00:00
perf(agent): skip plugin validation for gateway dispatch
This commit is contained in:
@@ -206,7 +206,7 @@ function createGatewayNormalCloseError() {
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock("../config/config.js", () => ({ getRuntimeConfig: loadConfig, loadConfig }));
|
||||
vi.mock("../config/io.js", () => ({ getRuntimeConfig: loadConfig, loadConfig }));
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway,
|
||||
isGatewayTransportError,
|
||||
@@ -284,6 +284,8 @@ describe("agentCliCommand", () => {
|
||||
expect(params.sessionKey).toBe("agent:main:incident-42");
|
||||
expect(params.sessionId).toBeUndefined();
|
||||
expect(params.to).toBeUndefined();
|
||||
expect(request.config).toBe(loadConfig.mock.results[0]?.value);
|
||||
expect(loadConfig).toHaveBeenCalledWith({ skipPluginValidation: true, pin: false });
|
||||
expect(agentCommand).not.toHaveBeenCalled();
|
||||
expect(loadAgentSessionModuleMock).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -811,6 +813,7 @@ describe("agentCliCommand", () => {
|
||||
});
|
||||
expect(fallbackAbort?.method).toBe("chat.abort");
|
||||
expect(fallbackAbort?.timeoutMs).toBe(2_000);
|
||||
expect(fallbackAbort?.config).toBe(loadConfig.mock.results[0]?.value);
|
||||
expect(fallbackAbort?.params).toEqual({
|
||||
sessionKey: "agent:main:main",
|
||||
runId: "pre-accepted-run",
|
||||
@@ -960,6 +963,7 @@ describe("agentCliCommand", () => {
|
||||
expect(fallbackAbort?.clientName).toBe("gateway-client");
|
||||
expect(fallbackAbort?.mode).toBe("backend");
|
||||
expect(fallbackAbort?.scopes).toEqual(["operator.admin"]);
|
||||
expect(fallbackAbort?.config).toBe(loadConfig.mock.results[0]?.value);
|
||||
expect(fallbackAbort?.params).toEqual({
|
||||
sessionKey: "agent:main:main",
|
||||
runId: "run-model-fallback",
|
||||
@@ -1434,6 +1438,10 @@ describe("agentCliCommand", () => {
|
||||
};
|
||||
expect(fallbackOpts.sessionId).toMatch(/^gateway-fallback-/);
|
||||
expect(fallbackOpts.sessionKey).toBe(`agent:ops:explicit:${fallbackOpts.sessionId}`);
|
||||
expect(loadConfig.mock.calls).toEqual([
|
||||
[{ skipPluginValidation: true, pin: false }],
|
||||
[{ skipPluginValidation: true, pin: false }],
|
||||
]);
|
||||
},
|
||||
{ agents: { list: [{ id: "ops", default: true }, { id: "main" }] } },
|
||||
);
|
||||
@@ -1621,6 +1629,7 @@ describe("agentCliCommand", () => {
|
||||
);
|
||||
expect(localOpts.agentId).toBe("ops");
|
||||
expect(localOpts.sessionKey).toBe("agent:ops:incident-42");
|
||||
expect(loadConfig).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../../packages/gateway-protocol/src/client-info.js";
|
||||
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope-config.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { CliDeps } from "../cli/deps.types.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { getRuntimeConfig } from "../config/config.js";
|
||||
import { getRuntimeConfig } from "../config/io.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
callGateway,
|
||||
@@ -150,6 +150,12 @@ function parseTimeoutSeconds(opts: { cfg: OpenClawConfig; timeout?: string }) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
function getGatewayDispatchConfig(): OpenClawConfig {
|
||||
// Scoped gateway turns need core agent/session/gateway fields only. The
|
||||
// running gateway owns plugin validation and plugin metadata freshness.
|
||||
return getRuntimeConfig({ skipPluginValidation: true, pin: false });
|
||||
}
|
||||
|
||||
function formatPayloadForLog(payload: {
|
||||
text?: string;
|
||||
mediaUrls?: string[];
|
||||
@@ -231,7 +237,9 @@ function normalizeSessionKeyOptsForDispatch(opts: AgentCliOpts): AgentCliOpts {
|
||||
isLegacySessionKey && !agentIdRaw && !isUnscopedSessionKeySentinel(rawSessionKey);
|
||||
const cfg =
|
||||
isLegacySessionKey && (agentIdRaw || shouldScopeDefaultAgentKey)
|
||||
? getRuntimeConfig()
|
||||
? opts.local === true
|
||||
? getRuntimeConfig()
|
||||
: getGatewayDispatchConfig()
|
||||
: undefined;
|
||||
const sessionKey = scopeLegacySessionKeyToAgent({
|
||||
agentId: agentIdRaw ?? (shouldScopeDefaultAgentKey ? resolveDefaultAgentId(cfg!) : undefined),
|
||||
@@ -401,6 +409,7 @@ async function abortAcceptedGatewayAgentRunWithGatewayCall(params: {
|
||||
signal: AgentCliSignal | undefined;
|
||||
runtime: RuntimeEnv;
|
||||
gatewayIdentity: AgentGatewayCallIdentity;
|
||||
config: OpenClawConfig;
|
||||
}): Promise<void> {
|
||||
const request: GatewayRequestFunction = async <T = Record<string, unknown>>(
|
||||
method: string,
|
||||
@@ -412,6 +421,7 @@ async function abortAcceptedGatewayAgentRunWithGatewayCall(params: {
|
||||
params: requestParams,
|
||||
timeoutMs: opts?.timeoutMs ?? undefined,
|
||||
expectFinal: opts?.expectFinal,
|
||||
config: params.config,
|
||||
...params.gatewayIdentity,
|
||||
});
|
||||
for (const [attempt, retryDelayMs] of [...GATEWAY_ABORT_RETRY_DELAYS_MS, 0].entries()) {
|
||||
@@ -495,7 +505,7 @@ async function resolveAgentIdForGatewayTimeoutFallback(
|
||||
return resolveAgentIdFromSessionKey(explicitSessionKey);
|
||||
}
|
||||
if (isUnscopedSessionKeySentinel(explicitSessionKey)) {
|
||||
return resolveDefaultAgentId(getRuntimeConfig());
|
||||
return resolveDefaultAgentId(getGatewayDispatchConfig());
|
||||
}
|
||||
|
||||
const agentIdRaw = opts.agent?.trim();
|
||||
@@ -506,7 +516,7 @@ async function resolveAgentIdForGatewayTimeoutFallback(
|
||||
if (!opts.to && !opts.sessionId) {
|
||||
return undefined;
|
||||
}
|
||||
const cfg = getRuntimeConfig();
|
||||
const cfg = getGatewayDispatchConfig();
|
||||
const { resolveSessionKeyForRequest } = await loadAgentSessionModule();
|
||||
const resolvedSessionKey = resolveSessionKeyForRequest({
|
||||
cfg,
|
||||
@@ -558,7 +568,7 @@ async function agentViaGatewayCommand(
|
||||
);
|
||||
}
|
||||
|
||||
const cfg = getRuntimeConfig();
|
||||
const cfg = getGatewayDispatchConfig();
|
||||
const agentIdRaw = opts.agent?.trim();
|
||||
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined;
|
||||
if (agentId) {
|
||||
@@ -638,6 +648,7 @@ async function agentViaGatewayCommand(
|
||||
},
|
||||
expectFinal: true,
|
||||
timeoutMs: gatewayTimeoutMs,
|
||||
config: cfg,
|
||||
signal: signalBridge.signal,
|
||||
onAccepted: (payload) => {
|
||||
acceptedGatewayRun = true;
|
||||
@@ -670,6 +681,7 @@ async function agentViaGatewayCommand(
|
||||
signal: signalBridge.getReceivedSignal(),
|
||||
runtime,
|
||||
gatewayIdentity,
|
||||
config: cfg,
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getRuntimeConfig, clearConfigCache, clearRuntimeConfigSnapshot } from "./config.js";
|
||||
import {
|
||||
getRuntimeConfig,
|
||||
clearConfigCache,
|
||||
clearRuntimeConfigSnapshot,
|
||||
getRuntimeConfigSnapshot,
|
||||
} from "./config.js";
|
||||
import { withTempHomeConfig } from "./test-helpers.js";
|
||||
|
||||
describe("talk config validation fail-closed behavior", () => {
|
||||
@@ -9,6 +14,20 @@ describe("talk config validation fail-closed behavior", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("can load an unpinned runtime config without replacing the process snapshot", async () => {
|
||||
await withTempHomeConfig({ gateway: { port: 19002 } }, async () => {
|
||||
const unpinned = getRuntimeConfig({ skipPluginValidation: true, pin: false });
|
||||
|
||||
expect(unpinned.gateway?.port).toBe(19002);
|
||||
expect(getRuntimeConfigSnapshot()).toBeNull();
|
||||
|
||||
const pinned = getRuntimeConfig();
|
||||
|
||||
expect(pinned.gateway?.port).toBe(19002);
|
||||
expect(getRuntimeConfigSnapshot()).toBe(pinned);
|
||||
});
|
||||
});
|
||||
|
||||
async function expectInvalidTalkConfig(config: unknown, messagePattern: RegExp) {
|
||||
await withTempHomeConfig(config, async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
@@ -2540,16 +2540,25 @@ export function projectConfigOntoRuntimeSourceSnapshot(config: OpenClawConfig):
|
||||
return coerceConfig(applyMergePatch(projectedSource, runtimePatch));
|
||||
}
|
||||
|
||||
export function loadConfig(options?: { skipPluginValidation?: boolean }): OpenClawConfig {
|
||||
export function loadConfig(options?: {
|
||||
skipPluginValidation?: boolean;
|
||||
pin?: boolean;
|
||||
}): OpenClawConfig {
|
||||
const loadFresh = () =>
|
||||
createConfigIO(options?.skipPluginValidation ? { pluginValidation: "skip" } : {}).loadConfig();
|
||||
if (options?.pin === false) {
|
||||
return loadFresh();
|
||||
}
|
||||
// First successful load becomes the process snapshot. Long-lived runtimes
|
||||
// should swap this snapshot via explicit reload/watcher paths instead of
|
||||
// reparsing openclaw.json on hot code paths.
|
||||
return loadPinnedRuntimeConfig(() =>
|
||||
createConfigIO(options?.skipPluginValidation ? { pluginValidation: "skip" } : {}).loadConfig(),
|
||||
);
|
||||
return loadPinnedRuntimeConfig(loadFresh);
|
||||
}
|
||||
|
||||
export function getRuntimeConfig(options?: { skipPluginValidation?: boolean }): OpenClawConfig {
|
||||
export function getRuntimeConfig(options?: {
|
||||
skipPluginValidation?: boolean;
|
||||
pin?: boolean;
|
||||
}): OpenClawConfig {
|
||||
return loadConfig(options);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user