mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-13 18:21:27 +00:00
refactor: share command config resolution
This commit is contained in:
@@ -10,8 +10,8 @@ import {
|
||||
supportsXHighThinking,
|
||||
type VerboseLevel,
|
||||
} from "../auto-reply/thinking.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../cli/command-config-resolution.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
|
||||
import { getAgentRuntimeCommandSecretTargetIds } from "../cli/command-secret-targets.js";
|
||||
import { type CliDeps, createDefaultDeps } from "../cli/deps.js";
|
||||
import {
|
||||
@@ -178,10 +178,11 @@ async function prepareAgentCommandExecution(
|
||||
}
|
||||
return loadedRaw;
|
||||
})();
|
||||
const { resolvedConfig: cfg, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
const { resolvedConfig: cfg } = await resolveCommandConfigWithSecrets({
|
||||
config: loadedRaw,
|
||||
commandName: "agent",
|
||||
targetIds: getAgentRuntimeCommandSecretTargetIds(),
|
||||
runtime,
|
||||
});
|
||||
setRuntimeConfigSnapshot(cfg, sourceConfig);
|
||||
const normalizedSpawned = normalizeSpawnedRunMetadata({
|
||||
@@ -191,9 +192,6 @@ async function prepareAgentCommandExecution(
|
||||
groupSpace: opts.groupSpace,
|
||||
workspaceDir: opts.workspaceDir,
|
||||
});
|
||||
for (const entry of diagnostics) {
|
||||
runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
const agentIdOverrideRaw = opts.agentId?.trim();
|
||||
const agentIdOverride = agentIdOverrideRaw ? normalizeAgentId(agentIdOverrideRaw) : undefined;
|
||||
if (agentIdOverride) {
|
||||
|
||||
82
src/cli/command-config-resolution.test.ts
Normal file
82
src/cli/command-config-resolution.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
resolveCommandSecretRefsViaGateway: vi.fn(),
|
||||
applyPluginAutoEnable: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./command-secret-gateway.js", () => ({
|
||||
resolveCommandSecretRefsViaGateway: mocks.resolveCommandSecretRefsViaGateway,
|
||||
}));
|
||||
|
||||
vi.mock("../config/plugin-auto-enable.js", () => ({
|
||||
applyPluginAutoEnable: mocks.applyPluginAutoEnable,
|
||||
}));
|
||||
|
||||
import { resolveCommandConfigWithSecrets } from "./command-config-resolution.js";
|
||||
|
||||
describe("resolveCommandConfigWithSecrets", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("logs diagnostics and preserves resolved config when auto-enable is off", async () => {
|
||||
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as const;
|
||||
const config = { channels: {} };
|
||||
const resolvedConfig = { channels: { telegram: {} } };
|
||||
const targetIds = new Set(["channels.telegram.token"]);
|
||||
mocks.resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig,
|
||||
diagnostics: ["resolved channels.telegram.token"],
|
||||
});
|
||||
|
||||
const result = await resolveCommandConfigWithSecrets({
|
||||
config,
|
||||
commandName: "status",
|
||||
targetIds,
|
||||
mode: "read_only_status",
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(mocks.resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith({
|
||||
config,
|
||||
commandName: "status",
|
||||
targetIds,
|
||||
mode: "read_only_status",
|
||||
});
|
||||
expect(runtime.log).toHaveBeenCalledWith("[secrets] resolved channels.telegram.token");
|
||||
expect(mocks.applyPluginAutoEnable).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
resolvedConfig,
|
||||
effectiveConfig: resolvedConfig,
|
||||
diagnostics: ["resolved channels.telegram.token"],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns auto-enabled config when requested", async () => {
|
||||
const resolvedConfig = { channels: {} };
|
||||
const effectiveConfig = { channels: {}, plugins: { allow: ["telegram"] } };
|
||||
mocks.resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig,
|
||||
diagnostics: [],
|
||||
});
|
||||
mocks.applyPluginAutoEnable.mockReturnValue({
|
||||
config: effectiveConfig,
|
||||
changes: ["enabled telegram"],
|
||||
});
|
||||
|
||||
const result = await resolveCommandConfigWithSecrets({
|
||||
config: resolvedConfig,
|
||||
commandName: "message",
|
||||
targetIds: new Set(["channels.telegram.token"]),
|
||||
autoEnable: true,
|
||||
env: { OPENCLAW_AUTO_ENABLE: "1" } as NodeJS.ProcessEnv,
|
||||
});
|
||||
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
|
||||
config: resolvedConfig,
|
||||
env: { OPENCLAW_AUTO_ENABLE: "1" },
|
||||
});
|
||||
expect(result.effectiveConfig).toBe(effectiveConfig);
|
||||
});
|
||||
});
|
||||
46
src/cli/command-config-resolution.ts
Normal file
46
src/cli/command-config-resolution.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import {
|
||||
type CommandSecretResolutionMode,
|
||||
resolveCommandSecretRefsViaGateway,
|
||||
} from "./command-secret-gateway.js";
|
||||
|
||||
export async function resolveCommandConfigWithSecrets<TConfig extends OpenClawConfig>(params: {
|
||||
config: TConfig;
|
||||
commandName: string;
|
||||
targetIds: Set<string>;
|
||||
mode?: CommandSecretResolutionMode;
|
||||
allowedPaths?: Set<string>;
|
||||
runtime?: RuntimeEnv;
|
||||
autoEnable?: boolean;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{
|
||||
resolvedConfig: TConfig;
|
||||
effectiveConfig: TConfig;
|
||||
diagnostics: string[];
|
||||
}> {
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
config: params.config,
|
||||
commandName: params.commandName,
|
||||
targetIds: params.targetIds,
|
||||
...(params.mode ? { mode: params.mode } : {}),
|
||||
...(params.allowedPaths ? { allowedPaths: params.allowedPaths } : {}),
|
||||
});
|
||||
if (params.runtime) {
|
||||
for (const entry of diagnostics) {
|
||||
params.runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
}
|
||||
const effectiveConfig = params.autoEnable
|
||||
? applyPluginAutoEnable({
|
||||
config: resolvedConfig,
|
||||
env: params.env ?? process.env,
|
||||
}).config
|
||||
: resolvedConfig;
|
||||
return {
|
||||
resolvedConfig: resolvedConfig as TConfig,
|
||||
effectiveConfig: effectiveConfig as TConfig,
|
||||
diagnostics,
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getChannelPlugin } from "../../channels/plugins/index.js";
|
||||
import type { ChannelResolveKind, ChannelResolveResult } from "../../channels/plugins/types.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../../cli/command-secret-gateway.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../../config/config.js";
|
||||
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
@@ -108,19 +107,14 @@ function formatResolveResult(result: ResolveResult): string {
|
||||
export async function channelsResolveCommand(opts: ChannelsResolveOptions, runtime: RuntimeEnv) {
|
||||
const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
|
||||
const loadedRaw = loadConfig();
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
let { effectiveConfig: cfg } = await resolveCommandConfigWithSecrets({
|
||||
config: loadedRaw,
|
||||
commandName: "channels resolve",
|
||||
targetIds: getChannelsCommandSecretTargetIds(),
|
||||
mode: "read_only_operational",
|
||||
runtime,
|
||||
autoEnable: true,
|
||||
});
|
||||
let cfg = applyPluginAutoEnable({
|
||||
config: resolvedConfig,
|
||||
env: process.env,
|
||||
}).config;
|
||||
for (const entry of diagnostics) {
|
||||
runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
const entries = (opts.entries ?? []).map((entry) => entry.trim()).filter(Boolean);
|
||||
if (entries.length === 0) {
|
||||
throw new Error("At least one entry is required.");
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { type ChannelId, getChannelPlugin } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
type CommandSecretResolutionMode,
|
||||
resolveCommandSecretRefsViaGateway,
|
||||
} from "../../cli/command-secret-gateway.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import type { CommandSecretResolutionMode } from "../../cli/command-secret-gateway.js";
|
||||
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
@@ -28,16 +26,14 @@ export async function requireValidConfig(
|
||||
if (!cfg) {
|
||||
return null;
|
||||
}
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
const { effectiveConfig } = await resolveCommandConfigWithSecrets({
|
||||
config: cfg,
|
||||
commandName: secretResolution?.commandName ?? "channels",
|
||||
targetIds: getChannelsCommandSecretTargetIds(),
|
||||
mode: secretResolution?.mode,
|
||||
runtime,
|
||||
});
|
||||
for (const entry of diagnostics) {
|
||||
runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
return resolvedConfig;
|
||||
return effectiveConfig;
|
||||
}
|
||||
|
||||
export function formatAccountLabel(params: { accountId: string; name?: string }) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
buildReadOnlySourceChannelAccountSnapshot,
|
||||
} from "../../channels/plugins/status.js";
|
||||
import type { ChannelAccountSnapshot } from "../../channels/plugins/types.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../../cli/command-secret-gateway.js";
|
||||
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import { withProgress } from "../../cli/progress.js";
|
||||
import { type OpenClawConfig, readConfigFileSnapshot } from "../../config/config.js";
|
||||
@@ -311,15 +311,13 @@ export async function channelsStatusCommand(
|
||||
if (!cfg) {
|
||||
return;
|
||||
}
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
const { resolvedConfig } = await resolveCommandConfigWithSecrets({
|
||||
config: cfg,
|
||||
commandName: "channels status",
|
||||
targetIds: getChannelsCommandSecretTargetIds(),
|
||||
mode: "read_only_status",
|
||||
runtime,
|
||||
});
|
||||
for (const entry of diagnostics) {
|
||||
runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const mode = cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||
runtime.log(
|
||||
|
||||
@@ -3,13 +3,12 @@ import {
|
||||
CHANNEL_MESSAGE_ACTION_NAMES,
|
||||
type ChannelMessageActionName,
|
||||
} from "../channels/plugins/types.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../cli/command-config-resolution.js";
|
||||
import { getScopedChannelsCommandSecretTargets } from "../cli/command-secret-targets.js";
|
||||
import { resolveMessageSecretScope } from "../cli/message-secret-scope.js";
|
||||
import { createOutboundSendDeps, type CliDeps } from "../cli/outbound-send-deps.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
|
||||
import { runMessageAction } from "../infra/outbound/message-action-runner.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
@@ -33,19 +32,14 @@ export async function messageCommand(
|
||||
channel: scope.channel,
|
||||
accountId: scope.accountId,
|
||||
});
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
const { effectiveConfig: cfg } = await resolveCommandConfigWithSecrets({
|
||||
config: loadedRaw,
|
||||
commandName: "message",
|
||||
targetIds: scopedTargets.targetIds,
|
||||
...(scopedTargets.allowedPaths ? { allowedPaths: scopedTargets.allowedPaths } : {}),
|
||||
runtime,
|
||||
autoEnable: true,
|
||||
});
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: resolvedConfig,
|
||||
env: process.env,
|
||||
}).config;
|
||||
for (const entry of diagnostics) {
|
||||
runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
const rawAction = typeof opts.action === "string" ? opts.action.trim() : "";
|
||||
const actionInput = rawAction || "send";
|
||||
const actionMatch = (CHANNEL_MESSAGE_ACTION_NAMES as readonly string[]).find(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import {
|
||||
getRuntimeConfig,
|
||||
@@ -5,7 +6,6 @@ import {
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
getModelsCommandSecretTargetIds,
|
||||
resolveCommandSecretRefsViaGateway,
|
||||
} from "./load-config.runtime.js";
|
||||
|
||||
export type LoadedModelsConfig = {
|
||||
@@ -32,16 +32,12 @@ export async function loadModelsConfigWithSource(params: {
|
||||
}): Promise<LoadedModelsConfig> {
|
||||
const runtimeConfig = getRuntimeConfig();
|
||||
const sourceConfig = await loadSourceConfigSnapshot(runtimeConfig);
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandSecretRefsViaGateway({
|
||||
const { resolvedConfig, diagnostics } = await resolveCommandConfigWithSecrets({
|
||||
config: runtimeConfig,
|
||||
commandName: params.commandName,
|
||||
targetIds: getModelsCommandSecretTargetIds(),
|
||||
runtime: params.runtime,
|
||||
});
|
||||
if (params.runtime) {
|
||||
for (const entry of diagnostics) {
|
||||
params.runtime.log(`[secrets] ${entry}`);
|
||||
}
|
||||
}
|
||||
setRuntimeConfigSnapshot(resolvedConfig, sourceConfig);
|
||||
return {
|
||||
sourceConfig,
|
||||
|
||||
Reference in New Issue
Block a user