Skip to content

Commit 67faef0

Browse files
committed
perf(agent): skip plugin validation for gateway dispatch
1 parent 2106714 commit 67faef0

4 files changed

Lines changed: 62 additions & 13 deletions

File tree

src/commands/agent-via-gateway.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ function createGatewayNormalCloseError() {
206206
});
207207
}
208208

209-
vi.mock("../config/config.js", () => ({ getRuntimeConfig: loadConfig, loadConfig }));
209+
vi.mock("../config/io.js", () => ({ getRuntimeConfig: loadConfig, loadConfig }));
210210
vi.mock("../gateway/call.js", () => ({
211211
callGateway,
212212
isGatewayTransportError,
@@ -284,6 +284,8 @@ describe("agentCliCommand", () => {
284284
expect(params.sessionKey).toBe("agent:main:incident-42");
285285
expect(params.sessionId).toBeUndefined();
286286
expect(params.to).toBeUndefined();
287+
expect(request.config).toBe(loadConfig.mock.results[0]?.value);
288+
expect(loadConfig).toHaveBeenCalledWith({ skipPluginValidation: true, pin: false });
287289
expect(agentCommand).not.toHaveBeenCalled();
288290
expect(loadAgentSessionModuleMock).not.toHaveBeenCalled();
289291
});
@@ -811,6 +813,7 @@ describe("agentCliCommand", () => {
811813
});
812814
expect(fallbackAbort?.method).toBe("chat.abort");
813815
expect(fallbackAbort?.timeoutMs).toBe(2_000);
816+
expect(fallbackAbort?.config).toBe(loadConfig.mock.results[0]?.value);
814817
expect(fallbackAbort?.params).toEqual({
815818
sessionKey: "agent:main:main",
816819
runId: "pre-accepted-run",
@@ -960,6 +963,7 @@ describe("agentCliCommand", () => {
960963
expect(fallbackAbort?.clientName).toBe("gateway-client");
961964
expect(fallbackAbort?.mode).toBe("backend");
962965
expect(fallbackAbort?.scopes).toEqual(["operator.admin"]);
966+
expect(fallbackAbort?.config).toBe(loadConfig.mock.results[0]?.value);
963967
expect(fallbackAbort?.params).toEqual({
964968
sessionKey: "agent:main:main",
965969
runId: "run-model-fallback",
@@ -1434,6 +1438,10 @@ describe("agentCliCommand", () => {
14341438
};
14351439
expect(fallbackOpts.sessionId).toMatch(/^gateway-fallback-/);
14361440
expect(fallbackOpts.sessionKey).toBe(`agent:ops:explicit:${fallbackOpts.sessionId}`);
1441+
expect(loadConfig.mock.calls).toEqual([
1442+
[{ skipPluginValidation: true, pin: false }],
1443+
[{ skipPluginValidation: true, pin: false }],
1444+
]);
14371445
},
14381446
{ agents: { list: [{ id: "ops", default: true }, { id: "main" }] } },
14391447
);
@@ -1621,6 +1629,7 @@ describe("agentCliCommand", () => {
16211629
);
16221630
expect(localOpts.agentId).toBe("ops");
16231631
expect(localOpts.sessionKey).toBe("agent:ops:incident-42");
1632+
expect(loadConfig).toHaveBeenCalledWith();
16241633
});
16251634
});
16261635

src/commands/agent-via-gateway.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import {
44
GATEWAY_CLIENT_MODES,
55
GATEWAY_CLIENT_NAMES,
66
} from "../../packages/gateway-protocol/src/client-info.js";
7-
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js";
7+
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope-config.js";
88
import { formatCliCommand } from "../cli/command-format.js";
99
import type { CliDeps } from "../cli/deps.types.js";
1010
import { withProgress } from "../cli/progress.js";
11-
import { getRuntimeConfig } from "../config/config.js";
11+
import { getRuntimeConfig } from "../config/io.js";
1212
import type { OpenClawConfig } from "../config/types.openclaw.js";
1313
import {
1414
callGateway,
@@ -150,6 +150,12 @@ function parseTimeoutSeconds(opts: { cfg: OpenClawConfig; timeout?: string }) {
150150
return raw;
151151
}
152152

153+
function getGatewayDispatchConfig(): OpenClawConfig {
154+
// Scoped gateway turns need core agent/session/gateway fields only. The
155+
// running gateway owns plugin validation and plugin metadata freshness.
156+
return getRuntimeConfig({ skipPluginValidation: true, pin: false });
157+
}
158+
153159
function formatPayloadForLog(payload: {
154160
text?: string;
155161
mediaUrls?: string[];
@@ -231,7 +237,9 @@ function normalizeSessionKeyOptsForDispatch(opts: AgentCliOpts): AgentCliOpts {
231237
isLegacySessionKey && !agentIdRaw && !isUnscopedSessionKeySentinel(rawSessionKey);
232238
const cfg =
233239
isLegacySessionKey && (agentIdRaw || shouldScopeDefaultAgentKey)
234-
? getRuntimeConfig()
240+
? opts.local === true
241+
? getRuntimeConfig()
242+
: getGatewayDispatchConfig()
235243
: undefined;
236244
const sessionKey = scopeLegacySessionKeyToAgent({
237245
agentId: agentIdRaw ?? (shouldScopeDefaultAgentKey ? resolveDefaultAgentId(cfg!) : undefined),
@@ -401,6 +409,7 @@ async function abortAcceptedGatewayAgentRunWithGatewayCall(params: {
401409
signal: AgentCliSignal | undefined;
402410
runtime: RuntimeEnv;
403411
gatewayIdentity: AgentGatewayCallIdentity;
412+
config: OpenClawConfig;
404413
}): Promise<void> {
405414
const request: GatewayRequestFunction = async <T = Record<string, unknown>>(
406415
method: string,
@@ -412,6 +421,7 @@ async function abortAcceptedGatewayAgentRunWithGatewayCall(params: {
412421
params: requestParams,
413422
timeoutMs: opts?.timeoutMs ?? undefined,
414423
expectFinal: opts?.expectFinal,
424+
config: params.config,
415425
...params.gatewayIdentity,
416426
});
417427
for (const [attempt, retryDelayMs] of [...GATEWAY_ABORT_RETRY_DELAYS_MS, 0].entries()) {
@@ -495,7 +505,7 @@ async function resolveAgentIdForGatewayTimeoutFallback(
495505
return resolveAgentIdFromSessionKey(explicitSessionKey);
496506
}
497507
if (isUnscopedSessionKeySentinel(explicitSessionKey)) {
498-
return resolveDefaultAgentId(getRuntimeConfig());
508+
return resolveDefaultAgentId(getGatewayDispatchConfig());
499509
}
500510

501511
const agentIdRaw = opts.agent?.trim();
@@ -506,7 +516,7 @@ async function resolveAgentIdForGatewayTimeoutFallback(
506516
if (!opts.to && !opts.sessionId) {
507517
return undefined;
508518
}
509-
const cfg = getRuntimeConfig();
519+
const cfg = getGatewayDispatchConfig();
510520
const { resolveSessionKeyForRequest } = await loadAgentSessionModule();
511521
const resolvedSessionKey = resolveSessionKeyForRequest({
512522
cfg,
@@ -558,7 +568,7 @@ async function agentViaGatewayCommand(
558568
);
559569
}
560570

561-
const cfg = getRuntimeConfig();
571+
const cfg = getGatewayDispatchConfig();
562572
const agentIdRaw = opts.agent?.trim();
563573
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined;
564574
if (agentId) {
@@ -638,6 +648,7 @@ async function agentViaGatewayCommand(
638648
},
639649
expectFinal: true,
640650
timeoutMs: gatewayTimeoutMs,
651+
config: cfg,
641652
signal: signalBridge.signal,
642653
onAccepted: (payload) => {
643654
acceptedGatewayRun = true;
@@ -670,6 +681,7 @@ async function agentViaGatewayCommand(
670681
signal: signalBridge.getReceivedSignal(),
671682
runtime,
672683
gatewayIdentity,
684+
config: cfg,
673685
});
674686
}
675687
throw err;

src/config/config.talk-validation.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { beforeEach, describe, expect, it, vi } from "vitest";
2-
import { getRuntimeConfig, clearConfigCache, clearRuntimeConfigSnapshot } from "./config.js";
2+
import {
3+
getRuntimeConfig,
4+
clearConfigCache,
5+
clearRuntimeConfigSnapshot,
6+
getRuntimeConfigSnapshot,
7+
} from "./config.js";
38
import { withTempHomeConfig } from "./test-helpers.js";
49

510
describe("talk config validation fail-closed behavior", () => {
@@ -9,6 +14,20 @@ describe("talk config validation fail-closed behavior", () => {
914
vi.restoreAllMocks();
1015
});
1116

17+
it("can load an unpinned runtime config without replacing the process snapshot", async () => {
18+
await withTempHomeConfig({ gateway: { port: 19002 } }, async () => {
19+
const unpinned = getRuntimeConfig({ skipPluginValidation: true, pin: false });
20+
21+
expect(unpinned.gateway?.port).toBe(19002);
22+
expect(getRuntimeConfigSnapshot()).toBeNull();
23+
24+
const pinned = getRuntimeConfig();
25+
26+
expect(pinned.gateway?.port).toBe(19002);
27+
expect(getRuntimeConfigSnapshot()).toBe(pinned);
28+
});
29+
});
30+
1231
async function expectInvalidTalkConfig(config: unknown, messagePattern: RegExp) {
1332
await withTempHomeConfig(config, async () => {
1433
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});

src/config/io.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,16 +2540,25 @@ export function projectConfigOntoRuntimeSourceSnapshot(config: OpenClawConfig):
25402540
return coerceConfig(applyMergePatch(projectedSource, runtimePatch));
25412541
}
25422542

2543-
export function loadConfig(options?: { skipPluginValidation?: boolean }): OpenClawConfig {
2543+
export function loadConfig(options?: {
2544+
skipPluginValidation?: boolean;
2545+
pin?: boolean;
2546+
}): OpenClawConfig {
2547+
const loadFresh = () =>
2548+
createConfigIO(options?.skipPluginValidation ? { pluginValidation: "skip" } : {}).loadConfig();
2549+
if (options?.pin === false) {
2550+
return loadFresh();
2551+
}
25442552
// First successful load becomes the process snapshot. Long-lived runtimes
25452553
// should swap this snapshot via explicit reload/watcher paths instead of
25462554
// reparsing openclaw.json on hot code paths.
2547-
return loadPinnedRuntimeConfig(() =>
2548-
createConfigIO(options?.skipPluginValidation ? { pluginValidation: "skip" } : {}).loadConfig(),
2549-
);
2555+
return loadPinnedRuntimeConfig(loadFresh);
25502556
}
25512557

2552-
export function getRuntimeConfig(options?: { skipPluginValidation?: boolean }): OpenClawConfig {
2558+
export function getRuntimeConfig(options?: {
2559+
skipPluginValidation?: boolean;
2560+
pin?: boolean;
2561+
}): OpenClawConfig {
25532562
return loadConfig(options);
25542563
}
25552564

0 commit comments

Comments
 (0)