diff --git a/src/cli/command-secret-gateway.test.ts b/src/cli/command-secret-gateway.test.ts index d56aae2efef..6ec50cd6e5f 100644 --- a/src/cli/command-secret-gateway.test.ts +++ b/src/cli/command-secret-gateway.test.ts @@ -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", }, ], diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index fce86be4ae9..2f995f76bb8 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -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(fn: (home: string) => Promise): Promise { 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( - "../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>); - 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`, }); }); diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 90f7f729db9..98435d65afe 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -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; diff --git a/src/secrets/target-registry-query.ts b/src/secrets/target-registry-query.ts index a8065302c50..02a35c2d9c2 100644 --- a/src/secrets/target-registry-query.ts +++ b/src/secrets/target-registry-query.ts @@ -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; } | null = null; +let compiledCoreOpenClawTargetState: { + knownTargetIds: Set; + openClawCompiledSecretTargets: CompiledTargetRegistryEntry[]; + openClawTargetsById: Map; +} | null = null; + function buildTargetTypeIndex( compiledSecretTargetRegistry: CompiledTargetRegistryEntry[], ): Map { @@ -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): Set | null { if (targetIds === undefined) { return null; @@ -281,7 +302,13 @@ export function discoverConfigSecretTargetsByIds( targetIds?: Iterable, ): 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,