diff --git a/src/commands/daemon-install-auth-profiles-source.runtime.ts b/src/commands/daemon-install-auth-profiles-source.runtime.ts new file mode 100644 index 00000000000..13c06d695f6 --- /dev/null +++ b/src/commands/daemon-install-auth-profiles-source.runtime.ts @@ -0,0 +1 @@ +export { hasAnyAuthProfileStoreSource } from "../agents/auth-profiles/source-check.js"; diff --git a/src/commands/daemon-install-auth-profiles-store.runtime.ts b/src/commands/daemon-install-auth-profiles-store.runtime.ts new file mode 100644 index 00000000000..93a8f018cf9 --- /dev/null +++ b/src/commands/daemon-install-auth-profiles-store.runtime.ts @@ -0,0 +1 @@ +export { loadAuthProfileStoreForSecretsRuntime } from "../agents/auth-profiles/store.js"; diff --git a/src/commands/daemon-install-helpers.test.ts b/src/commands/daemon-install-helpers.test.ts index 5f36a1e89d6..7c46d0cb2fb 100644 --- a/src/commands/daemon-install-helpers.test.ts +++ b/src/commands/daemon-install-helpers.test.ts @@ -14,8 +14,11 @@ const mocks = vi.hoisted(() => ({ buildServiceEnvironment: vi.fn(), })); -vi.mock("../agents/auth-profiles.js", () => ({ +vi.mock("./daemon-install-auth-profiles-source.runtime.js", () => ({ hasAnyAuthProfileStoreSource: mocks.hasAnyAuthProfileStoreSource, +})); + +vi.mock("./daemon-install-auth-profiles-store.runtime.js", () => ({ loadAuthProfileStoreForSecretsRuntime: mocks.loadAuthProfileStoreForSecretsRuntime, })); @@ -301,6 +304,36 @@ describe("buildGatewayInstallPlan", () => { expect(plan.environment.OPENCLAW_PORT).toBe("3000"); }); + it("uses the provided authStore without probing auth-profile runtime", async () => { + mockNodeGatewayPlanFixture({ + serviceEnvironment: { + OPENCLAW_PORT: "3000", + }, + }); + + const plan = await buildGatewayInstallPlan({ + env: { + OPENAI_API_KEY: "sk-openai-test", + }, + port: 3000, + runtime: "node", + authStore: { + version: 1, + profiles: { + "openai:default": { + type: "api_key", + provider: "openai", + keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, + }, + }, + }, + }); + + expect(plan.environment.OPENAI_API_KEY).toBe("sk-openai-test"); + expect(mocks.hasAnyAuthProfileStoreSource).not.toHaveBeenCalled(); + expect(mocks.loadAuthProfileStoreForSecretsRuntime).not.toHaveBeenCalled(); + }); + it("merges env-backed auth-profile refs into the service environment", async () => { mockNodeGatewayPlanFixture({ serviceEnvironment: { diff --git a/src/commands/daemon-install-helpers.ts b/src/commands/daemon-install-helpers.ts index 7583ef136f1..0d811cdd838 100644 --- a/src/commands/daemon-install-helpers.ts +++ b/src/commands/daemon-install-helpers.ts @@ -1,10 +1,6 @@ import os from "node:os"; import path from "node:path"; -import { - hasAnyAuthProfileStoreSource, - loadAuthProfileStoreForSecretsRuntime, - type AuthProfileStore, -} from "../agents/auth-profiles.js"; +import type { AuthProfileStore } from "../agents/auth-profiles/types.js"; import { formatCliCommand } from "../cli/command-format.js"; import { collectDurableServiceEnvVars } from "../config/state-dir-dotenv.js"; import type { OpenClawConfig } from "../config/types.js"; @@ -34,15 +30,41 @@ export type GatewayInstallPlan = { const MANAGED_SERVICE_ENV_KEYS_VAR = "OPENCLAW_SERVICE_MANAGED_ENV_KEYS"; -function collectAuthProfileServiceEnvVars(params: { +let daemonInstallAuthProfileSourceRuntimePromise: + | Promise + | undefined; +let daemonInstallAuthProfileStoreRuntimePromise: + | Promise + | undefined; + +function loadDaemonInstallAuthProfileSourceRuntime() { + daemonInstallAuthProfileSourceRuntimePromise ??= + import("./daemon-install-auth-profiles-source.runtime.js"); + return daemonInstallAuthProfileSourceRuntimePromise; +} + +function loadDaemonInstallAuthProfileStoreRuntime() { + daemonInstallAuthProfileStoreRuntimePromise ??= + import("./daemon-install-auth-profiles-store.runtime.js"); + return daemonInstallAuthProfileStoreRuntimePromise; +} + +async function collectAuthProfileServiceEnvVars(params: { env: Record; authStore?: AuthProfileStore; warn?: DaemonInstallWarnFn; -}): Record { - const authStore = - params.authStore ?? +}): Promise> { + let authStore = params.authStore; + if (!authStore) { // Keep the daemon install cold path cheap when there is no auth store to read. - (hasAnyAuthProfileStoreSource() ? loadAuthProfileStoreForSecretsRuntime() : undefined); + const { hasAnyAuthProfileStoreSource } = await loadDaemonInstallAuthProfileSourceRuntime(); + if (!hasAnyAuthProfileStoreSource()) { + return {}; + } + const { loadAuthProfileStoreForSecretsRuntime } = + await loadDaemonInstallAuthProfileStoreRuntime(); + authStore = loadAuthProfileStoreForSecretsRuntime(); + } if (!authStore) { return {}; } @@ -190,24 +212,24 @@ function collectPreservedExistingServiceEnvVars( return preserved; } -function buildGatewayInstallEnvironment(params: { +async function buildGatewayInstallEnvironment(params: { env: Record; config?: OpenClawConfig; authStore?: AuthProfileStore; warn?: DaemonInstallWarnFn; serviceEnvironment: Record; existingEnvironment?: Record; -}): Record { +}): Promise> { const managedEnvironment: Record = { ...collectDurableServiceEnvVars({ env: params.env, config: params.config, }), - ...collectAuthProfileServiceEnvVars({ + ...(await collectAuthProfileServiceEnvVars({ env: params.env, authStore: params.authStore, warn: params.warn, - }), + })), }; const environment: Record = { ...collectPreservedExistingServiceEnvVars( @@ -277,7 +299,7 @@ export async function buildGatewayInstallPlan(params: { return { programArguments, workingDirectory, - environment: buildGatewayInstallEnvironment({ + environment: await buildGatewayInstallEnvironment({ env: params.env, config: params.config, authStore: params.authStore,