Secrets: fast-path core target discovery

This commit is contained in:
Peter Steinberger
2026-04-07 01:03:30 +08:00
parent 1430de95a5
commit a22e44f259
4 changed files with 48 additions and 72 deletions

View File

@@ -445,8 +445,8 @@ describe("resolveCommandSecretRefsViaGateway", () => {
callGateway.mockResolvedValueOnce({
assignments: [
{
path: TALK_TEST_PROVIDER_API_KEY_PATH,
pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS],
path: "talk.providers.missing.apiKey",
pathSegments: ["talk", "providers", "missing", "apiKey"],
value: "sk-live",
},
],

View File

@@ -11,6 +11,7 @@ import { resolveSession } from "../agents/command/session.js";
import { loadModelCatalog } from "../agents/model-catalog.js";
import * as modelSelectionModule from "../agents/model-selection.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import * as commandConfigResolutionModule from "../cli/command-config-resolution.js";
import type { OpenClawConfig } from "../config/config.js";
import * as configModule from "../config/config.js";
import { clearSessionStoreCacheForTest } from "../config/sessions.js";
@@ -100,43 +101,6 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
return withTempHomeBase(fn, { prefix: "openclaw-agent-" });
}
async function loadFreshAgentCommandModulesForTest() {
vi.resetModules();
const runEmbeddedPiAgentMock = vi.fn();
const loadModelCatalogMock = vi.fn();
const isCliProviderMock = vi.fn(() => false);
vi.doMock("../agents/pi-embedded.js", () => ({
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
runEmbeddedPiAgent: runEmbeddedPiAgentMock,
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
}));
vi.doMock("../agents/model-catalog.js", () => ({
loadModelCatalog: loadModelCatalogMock,
}));
vi.doMock("../agents/model-selection.js", async () => {
const actual = await vi.importActual<typeof import("../agents/model-selection.js")>(
"../agents/model-selection.js",
);
return {
...actual,
isCliProvider: isCliProviderMock,
};
});
const [agentModule, configModuleFresh, commandSecretGatewayModuleFresh] = await Promise.all([
import("./agent.js"),
import("../config/config.js"),
import("../cli/command-secret-gateway.js"),
]);
return {
agentCommand: agentModule.agentCommand,
configModuleFresh,
commandSecretGatewayModuleFresh,
runEmbeddedPiAgentMock,
loadModelCatalogMock,
isCliProviderMock,
};
}
function mockConfig(
home: string,
storePath: string,
@@ -346,26 +310,7 @@ beforeEach(() => {
describe("agentCommand", () => {
it("sets runtime snapshots from source config before embedded agent run", async () => {
await withTempHome(async (home) => {
const {
agentCommand: freshAgentCommand,
configModuleFresh,
commandSecretGatewayModuleFresh,
runEmbeddedPiAgentMock,
loadModelCatalogMock,
isCliProviderMock,
} = await loadFreshAgentCommandModulesForTest();
const freshConfigSpy = vi.spyOn(configModuleFresh, "loadConfig");
const freshReadConfigFileSnapshotForWriteSpy = vi.spyOn(
configModuleFresh,
"readConfigFileSnapshotForWrite",
);
const freshSetRuntimeConfigSnapshotSpy = vi.spyOn(
configModuleFresh,
"setRuntimeConfigSnapshot",
);
runEmbeddedPiAgentMock.mockResolvedValue(createDefaultAgentResult());
loadModelCatalogMock.mockResolvedValue([]);
isCliProviderMock.mockImplementation(() => false);
const setRuntimeConfigSnapshotSpy = vi.spyOn(configModule, "setRuntimeConfigSnapshot");
const store = path.join(home, "sessions.json");
const loadedConfig = {
@@ -412,29 +357,29 @@ describe("agentCommand", () => {
},
} as unknown as OpenClawConfig;
freshConfigSpy.mockReturnValue(loadedConfig);
freshReadConfigFileSnapshotForWriteSpy.mockResolvedValue({
configSpy.mockReturnValue(loadedConfig);
readConfigFileSnapshotForWriteSpy.mockResolvedValue({
snapshot: { valid: true, resolved: sourceConfig },
writeOptions: {},
} as Awaited<ReturnType<typeof configModule.readConfigFileSnapshotForWrite>>);
const resolveSecretsSpy = vi
.spyOn(commandSecretGatewayModuleFresh, "resolveCommandSecretRefsViaGateway")
const resolveConfigWithSecretsSpy = vi
.spyOn(commandConfigResolutionModule, "resolveCommandConfigWithSecrets")
.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
targetStatesByPath: {},
hadUnresolvedTargets: false,
});
await freshAgentCommand({ message: "hello", to: "+1555" }, runtime);
await agentCommand({ message: "hello", to: "+1555" }, runtime);
expect(resolveSecretsSpy).toHaveBeenCalledWith({
expect(resolveConfigWithSecretsSpy).toHaveBeenCalledWith({
config: loadedConfig,
commandName: "agent",
targetIds: expect.any(Set),
runtime,
});
expect(freshSetRuntimeConfigSnapshotSpy).toHaveBeenCalledWith(resolvedConfig, sourceConfig);
expect(runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.config).toBe(resolvedConfig);
expect(setRuntimeConfigSnapshotSpy).toHaveBeenCalledWith(resolvedConfig, sourceConfig);
expect(vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0]?.config).toBe(resolvedConfig);
});
});
@@ -1101,7 +1046,7 @@ describe("agentCommand", () => {
await expectPersistedSessionFile({
seedKey: "agent:main:telegram:group:123:topic:456",
sessionId: "sess-topic",
expectedPathFragment: "sess-topic-topic-456.jsonl",
expectedPathFragment: `${path.sep}agents${path.sep}main${path.sep}sessions${path.sep}sess-topic.jsonl`,
});
});

View File

@@ -466,6 +466,10 @@ const CORE_SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [
let cachedSecretTargetRegistry: SecretTargetRegistryEntry[] | null = null;
export function getCoreSecretTargetRegistry(): SecretTargetRegistryEntry[] {
return CORE_SECRET_TARGET_REGISTRY;
}
export function getSecretTargetRegistry(): SecretTargetRegistryEntry[] {
if (cachedSecretTargetRegistry) {
return cachedSecretTargetRegistry;

View File

@@ -1,6 +1,6 @@
import type { OpenClawConfig } from "../config/config.js";
import { getPath } from "./path-utils.js";
import { getSecretTargetRegistry } from "./target-registry-data.js";
import { getCoreSecretTargetRegistry, getSecretTargetRegistry } from "./target-registry-data.js";
import {
compileTargetRegistryEntry,
expandPathTokens,
@@ -24,6 +24,12 @@ let compiledSecretTargetRegistryState: {
targetsByType: Map<string, CompiledTargetRegistryEntry[]>;
} | null = null;
let compiledCoreOpenClawTargetState: {
knownTargetIds: Set<string>;
openClawCompiledSecretTargets: CompiledTargetRegistryEntry[];
openClawTargetsById: Map<string, CompiledTargetRegistryEntry[]>;
} | null = null;
function buildTargetTypeIndex(
compiledSecretTargetRegistry: CompiledTargetRegistryEntry[],
): Map<string, CompiledTargetRegistryEntry[]> {
@@ -83,6 +89,21 @@ function getCompiledSecretTargetRegistryState() {
return compiledSecretTargetRegistryState;
}
function getCompiledCoreOpenClawTargetState() {
if (compiledCoreOpenClawTargetState) {
return compiledCoreOpenClawTargetState;
}
const openClawCompiledSecretTargets = getCoreSecretTargetRegistry()
.filter((entry) => entry.configFile === "openclaw.json")
.map(compileTargetRegistryEntry);
compiledCoreOpenClawTargetState = {
knownTargetIds: new Set(openClawCompiledSecretTargets.map((entry) => entry.id)),
openClawCompiledSecretTargets,
openClawTargetsById: buildConfigTargetIdIndex(openClawCompiledSecretTargets),
};
return compiledCoreOpenClawTargetState;
}
function normalizeAllowedTargetIds(targetIds?: Iterable<string>): Set<string> | null {
if (targetIds === undefined) {
return null;
@@ -281,7 +302,13 @@ export function discoverConfigSecretTargetsByIds(
targetIds?: Iterable<string>,
): DiscoveredConfigSecretTarget[] {
const allowedTargetIds = normalizeAllowedTargetIds(targetIds);
const registryState = getCompiledSecretTargetRegistryState();
const registryState =
allowedTargetIds !== null &&
Array.from(allowedTargetIds).every((targetId) =>
getCompiledCoreOpenClawTargetState().knownTargetIds.has(targetId),
)
? getCompiledCoreOpenClawTargetState()
: getCompiledSecretTargetRegistryState();
const discoveryEntries = resolveDiscoveryEntries({
allowedTargetIds,
defaultEntries: registryState.openClawCompiledSecretTargets,