mirror of https://github.com/openclaw/openclaw.git
refactor(config): use source snapshots for config mutations
This commit is contained in:
parent
f9bf76067f
commit
47216702f4
|
|
@ -1,7 +1,7 @@
|
|||
import type { Command } from "commander";
|
||||
import JSON5 from "json5";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
|
||||
import { readConfigFileSnapshot, replaceConfigFile } from "../config/config.js";
|
||||
import { formatConfigIssueLines, normalizeConfigIssues } from "../config/issue-format.js";
|
||||
import { CONFIG_PATH } from "../config/paths.js";
|
||||
import { isBlockedObjectKey } from "../config/prototype-keys.js";
|
||||
|
|
@ -1077,7 +1077,10 @@ export async function runConfigSet(opts: {
|
|||
return;
|
||||
}
|
||||
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
if (removedGatewayAuthPaths.length > 0) {
|
||||
runtime.log(
|
||||
info(
|
||||
|
|
@ -1155,7 +1158,11 @@ export async function runConfigUnset(opts: { path: string; runtime?: RuntimeEnv
|
|||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
await writeConfigFile(next, { unsetPaths: [parsedPath] });
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
writeOptions: { unsetPaths: [parsedPath] },
|
||||
});
|
||||
runtime.log(info(`Removed ${opts.path}. Restart the gateway to apply.`));
|
||||
} catch (err) {
|
||||
runtime.error(danger(String(err)));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Command } from "commander";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadConfig, writeConfigFile } from "../config/io.js";
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../config/config.js";
|
||||
import {
|
||||
buildWorkspaceHookStatus,
|
||||
type HookStatusEntry,
|
||||
|
|
@ -417,7 +417,8 @@ export function formatHooksCheck(report: HookStatusReport, opts: HooksCheckOptio
|
|||
}
|
||||
|
||||
export async function enableHook(hookName: string): Promise<void> {
|
||||
const config = loadConfig();
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const config = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const hook = resolveHookForToggle(buildHooksReport(config), hookName, { requireEligible: true });
|
||||
const nextConfig = buildConfigWithHookEnabled({
|
||||
config,
|
||||
|
|
@ -426,18 +427,25 @@ export async function enableHook(hookName: string): Promise<void> {
|
|||
ensureHooksEnabled: true,
|
||||
});
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
defaultRuntime.log(
|
||||
`${theme.success("✓")} Enabled hook: ${hook.emoji ?? "🔗"} ${theme.command(hookName)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function disableHook(hookName: string): Promise<void> {
|
||||
const config = loadConfig();
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const config = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const hook = resolveHookForToggle(buildHooksReport(config), hookName);
|
||||
const nextConfig = buildConfigWithHookEnabled({ config, hookName, enabled: false });
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
defaultRuntime.log(
|
||||
`${theme.warn("⏸")} Disabled hook: ${hook.emoji ?? "🔗"} ${theme.command(hookName)}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadConfig, writeConfigFile } from "../config/config.js";
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { parseClawHubPluginSpec } from "../infra/clawhub.js";
|
||||
|
|
@ -542,12 +542,16 @@ export function registerPluginsCli(program: Command) {
|
|||
.description("Enable a plugin in config")
|
||||
.argument("<id>", "Plugin id")
|
||||
.action(async (id: string) => {
|
||||
const cfg = loadConfig();
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const enableResult = enablePluginInConfig(cfg, id);
|
||||
let next: OpenClawConfig = enableResult.config;
|
||||
const slotResult = applySlotSelectionForPlugin(next, id);
|
||||
next = slotResult.config;
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
logSlotWarnings(slotResult.warnings);
|
||||
if (enableResult.enabled) {
|
||||
defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
||||
|
|
@ -565,9 +569,13 @@ export function registerPluginsCli(program: Command) {
|
|||
.description("Disable a plugin in config")
|
||||
.argument("<id>", "Plugin id")
|
||||
.action(async (id: string) => {
|
||||
const cfg = loadConfig();
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const next = setPluginEnabledInConfig(cfg, id, false);
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
defaultRuntime.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
|
||||
});
|
||||
|
||||
|
|
@ -580,7 +588,8 @@ export function registerPluginsCli(program: Command) {
|
|||
.option("--force", "Skip confirmation prompt", false)
|
||||
.option("--dry-run", "Show what would be removed without making changes", false)
|
||||
.action(async (id: string, opts: PluginUninstallOptions) => {
|
||||
const cfg = loadConfig();
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const report = buildPluginStatusReport({ config: cfg });
|
||||
const extensionsDir = path.join(resolveStateDir(process.env, os.homedir), "extensions");
|
||||
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
||||
|
|
@ -686,7 +695,10 @@ export function registerPluginsCli(program: Command) {
|
|||
defaultRuntime.log(theme.warn(warning));
|
||||
}
|
||||
|
||||
await writeConfigFile(result.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: result.config,
|
||||
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
||||
});
|
||||
|
||||
const removed: string[] = [];
|
||||
if (result.actions.entry) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { type HookInstallUpdate, recordHookInstall } from "../hooks/installs.js";
|
||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import { type PluginInstallUpdate, recordPluginInstall } from "../plugins/installs.js";
|
||||
|
|
@ -14,6 +14,7 @@ import {
|
|||
|
||||
export async function persistPluginInstall(params: {
|
||||
config: OpenClawConfig;
|
||||
baseHash?: string;
|
||||
pluginId: string;
|
||||
install: Omit<PluginInstallUpdate, "pluginId">;
|
||||
successMessage?: string;
|
||||
|
|
@ -26,7 +27,10 @@ export async function persistPluginInstall(params: {
|
|||
});
|
||||
const slotResult = applySlotSelectionForPlugin(next, params.pluginId);
|
||||
next = slotResult.config;
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(params.baseHash !== undefined ? { baseHash: params.baseHash } : {}),
|
||||
});
|
||||
logSlotWarnings(slotResult.warnings);
|
||||
if (params.warningMessage) {
|
||||
defaultRuntime.log(theme.warn(params.warningMessage));
|
||||
|
|
@ -38,6 +42,7 @@ export async function persistPluginInstall(params: {
|
|||
|
||||
export async function persistHookPackInstall(params: {
|
||||
config: OpenClawConfig;
|
||||
baseHash?: string;
|
||||
hookPackId: string;
|
||||
hooks: string[];
|
||||
install: Omit<HookInstallUpdate, "hookId" | "hooks">;
|
||||
|
|
@ -49,7 +54,10 @@ export async function persistHookPackInstall(params: {
|
|||
hooks: params.hooks,
|
||||
...params.install,
|
||||
});
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(params.baseHash !== undefined ? { baseHash: params.baseHash } : {}),
|
||||
});
|
||||
defaultRuntime.log(params.successMessage ?? `Installed hook pack: ${params.hookPackId}`);
|
||||
logHookPackRestartHint();
|
||||
return next;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { loadConfig, writeConfigFile } from "../config/config.js";
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../config/config.js";
|
||||
import type { HookInstallRecord } from "../config/types.hooks.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { updateNpmInstalledHookPacks } from "../hooks/update.js";
|
||||
|
|
@ -91,6 +91,7 @@ export async function runPluginUpdateCommand(params: {
|
|||
id?: string;
|
||||
opts: { all?: boolean; dryRun?: boolean };
|
||||
}) {
|
||||
const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
|
||||
const cfg = loadConfig();
|
||||
const logger = {
|
||||
info: (msg: string) => defaultRuntime.log(msg),
|
||||
|
|
@ -184,7 +185,10 @@ export async function runPluginUpdateCommand(params: {
|
|||
}
|
||||
|
||||
if (!params.opts.dryRun && (pluginResult.changed || hookResult.changed)) {
|
||||
await writeConfigFile(hookResult.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: hookResult.config,
|
||||
baseHash: (await sourceSnapshotPromise)?.hash,
|
||||
});
|
||||
defaultRuntime.log("Restart the gateway to load plugins and hooks.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import {
|
|||
import { doctorCommand } from "../../commands/doctor.js";
|
||||
import {
|
||||
readConfigFileSnapshot,
|
||||
replaceConfigFile,
|
||||
resolveGatewayPort,
|
||||
writeConfigFile,
|
||||
} from "../../config/config.js";
|
||||
import { formatConfigIssueLines } from "../../config/issue-format.js";
|
||||
import { asResolvedSourceConfig, asRuntimeConfig } from "../../config/materialize.js";
|
||||
|
|
@ -550,7 +550,10 @@ async function updatePluginsAfterCoreUpdate(params: {
|
|||
pluginConfig = npmResult.config;
|
||||
|
||||
if (syncResult.changed || npmResult.changed) {
|
||||
await writeConfigFile(pluginConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig: pluginConfig,
|
||||
baseHash: params.configSnapshot.hash,
|
||||
});
|
||||
}
|
||||
|
||||
if (params.opts.json) {
|
||||
|
|
@ -1020,9 +1023,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
|||
channel: requestedChannel,
|
||||
},
|
||||
};
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
baseHash: configSnapshot.hash,
|
||||
});
|
||||
postUpdateConfigSnapshot = {
|
||||
...configSnapshot,
|
||||
hash: undefined,
|
||||
parsed: next,
|
||||
sourceConfig: asResolvedSourceConfig(next),
|
||||
resolved: asResolvedSourceConfig(next),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { requireValidConfigSnapshot } from "./config-validation.js";
|
||||
import {
|
||||
requireValidConfigFileSnapshot as requireValidConfigFileSnapshotBase,
|
||||
requireValidConfigSnapshot,
|
||||
} from "./config-validation.js";
|
||||
|
||||
export function createQuietRuntime(runtime: RuntimeEnv): RuntimeEnv {
|
||||
return { ...runtime, log: () => {} };
|
||||
}
|
||||
|
||||
export async function requireValidConfigFileSnapshot(runtime: RuntimeEnv) {
|
||||
return await requireValidConfigFileSnapshotBase(runtime);
|
||||
}
|
||||
|
||||
export async function requireValidConfig(runtime: RuntimeEnv): Promise<OpenClawConfig | null> {
|
||||
return await requireValidConfigSnapshot(runtime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "../agents/agent-scope.js";
|
||||
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
describeBinding,
|
||||
parseBindingSpecs,
|
||||
} from "./agents.bindings.js";
|
||||
import { createQuietRuntime, requireValidConfig } from "./agents.command-shared.js";
|
||||
import { createQuietRuntime, requireValidConfigFileSnapshot } from "./agents.command-shared.js";
|
||||
import { applyAgentConfig, findAgentEntryIndex, listAgentEntries } from "./agents.config.js";
|
||||
import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js";
|
||||
import { applyAuthChoice, warnIfModelConfigLooksOff } from "./auth-choice.js";
|
||||
|
|
@ -53,10 +53,12 @@ export async function agentsAddCommand(
|
|||
runtime: RuntimeEnv = defaultRuntime,
|
||||
params?: { hasFlags?: boolean },
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
|
||||
if (!configSnapshot) {
|
||||
return;
|
||||
}
|
||||
const cfg = configSnapshot.sourceConfig ?? configSnapshot.config;
|
||||
const baseHash = configSnapshot.hash;
|
||||
|
||||
const workspaceFlag = opts.workspace?.trim();
|
||||
const nameInput = opts.name?.trim();
|
||||
|
|
@ -127,7 +129,10 @@ export async function agentsAddCommand(
|
|||
? applyAgentBindings(nextConfig, bindingParse.bindings)
|
||||
: { config: nextConfig, added: [], updated: [], skipped: [], conflicts: [] };
|
||||
|
||||
await writeConfigFile(bindingResult.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: bindingResult.config,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (!opts.json) {
|
||||
logConfigUpdated(runtime);
|
||||
}
|
||||
|
|
@ -342,7 +347,10 @@ export async function agentsAddCommand(
|
|||
}
|
||||
}
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
logConfigUpdated(runtime);
|
||||
await ensureWorkspaceAndSessions(workspaceDir, runtime, {
|
||||
skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { isRouteBinding, listRouteBindings } from "../config/bindings.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { AgentRouteBinding } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
|
|
@ -12,7 +12,7 @@ import {
|
|||
parseBindingSpecs,
|
||||
removeAgentBindings,
|
||||
} from "./agents.bindings.js";
|
||||
import { requireValidConfig } from "./agents.command-shared.js";
|
||||
import { requireValidConfig, requireValidConfigFileSnapshot } from "./agents.command-shared.js";
|
||||
import { buildAgentSummaries } from "./agents.config.js";
|
||||
|
||||
type AgentsBindingsListOptions = {
|
||||
|
|
@ -135,11 +135,13 @@ async function resolveConfigAndTargetAgentIdOrExit(params: {
|
|||
}): Promise<{
|
||||
cfg: NonNullable<Awaited<ReturnType<typeof requireValidConfig>>>;
|
||||
agentId: string;
|
||||
baseHash?: string;
|
||||
} | null> {
|
||||
const cfg = await requireValidConfig(params.runtime);
|
||||
if (!cfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(params.runtime);
|
||||
if (!configSnapshot) {
|
||||
return null;
|
||||
}
|
||||
const cfg = configSnapshot.sourceConfig ?? configSnapshot.config;
|
||||
const agentId = resolveTargetAgentIdOrExit({
|
||||
cfg,
|
||||
runtime: params.runtime,
|
||||
|
|
@ -148,7 +150,7 @@ async function resolveConfigAndTargetAgentIdOrExit(params: {
|
|||
if (!agentId) {
|
||||
return null;
|
||||
}
|
||||
return { cfg, agentId };
|
||||
return { cfg, agentId, baseHash: configSnapshot.hash };
|
||||
}
|
||||
|
||||
export async function agentsBindingsCommand(
|
||||
|
|
@ -213,7 +215,7 @@ export async function agentsBindCommand(
|
|||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const { cfg, agentId } = resolved;
|
||||
const { cfg, agentId, baseHash } = resolved;
|
||||
|
||||
const parsed = resolveParsedBindingsOrExit({
|
||||
runtime,
|
||||
|
|
@ -228,7 +230,10 @@ export async function agentsBindCommand(
|
|||
|
||||
const result = applyAgentBindings(cfg, parsed.bindings);
|
||||
if (result.added.length > 0 || result.updated.length > 0) {
|
||||
await writeConfigFile(result.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: result.config,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (!opts.json) {
|
||||
logConfigUpdated(runtime);
|
||||
}
|
||||
|
|
@ -290,7 +295,7 @@ export async function agentsUnbindCommand(
|
|||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const { cfg, agentId } = resolved;
|
||||
const { cfg, agentId, baseHash } = resolved;
|
||||
if (opts.all && (opts.bind?.length ?? 0) > 0) {
|
||||
runtime.error("Use either --all or --bind, not both.");
|
||||
runtime.exit(1);
|
||||
|
|
@ -311,7 +316,10 @@ export async function agentsUnbindCommand(
|
|||
bindings:
|
||||
[...keptRoutes, ...nonRoutes].length > 0 ? [...keptRoutes, ...nonRoutes] : undefined,
|
||||
};
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (!opts.json) {
|
||||
logConfigUpdated(runtime);
|
||||
}
|
||||
|
|
@ -341,7 +349,10 @@ export async function agentsUnbindCommand(
|
|||
|
||||
const result = removeAgentBindings(cfg, parsed.bindings);
|
||||
if (result.removed.length > 0) {
|
||||
await writeConfigFile(result.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: result.config,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (!opts.json) {
|
||||
logConfigUpdated(runtime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js";
|
||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { createClackPrompter } from "../wizard/clack-prompter.js";
|
||||
import { createQuietRuntime, requireValidConfig } from "./agents.command-shared.js";
|
||||
import { createQuietRuntime, requireValidConfigFileSnapshot } from "./agents.command-shared.js";
|
||||
import { findAgentEntryIndex, listAgentEntries, pruneAgentConfig } from "./agents.config.js";
|
||||
import { moveToTrash } from "./onboard-helpers.js";
|
||||
|
||||
|
|
@ -20,10 +20,12 @@ export async function agentsDeleteCommand(
|
|||
opts: AgentsDeleteOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
|
||||
if (!configSnapshot) {
|
||||
return;
|
||||
}
|
||||
const cfg = configSnapshot.sourceConfig ?? configSnapshot.config;
|
||||
const baseHash = configSnapshot.hash;
|
||||
|
||||
const input = opts.id?.trim();
|
||||
if (!input) {
|
||||
|
|
@ -70,7 +72,10 @@ export async function agentsDeleteCommand(
|
|||
const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId);
|
||||
|
||||
const result = pruneAgentConfig(cfg, agentId);
|
||||
await writeConfigFile(result.config);
|
||||
await replaceConfigFile({
|
||||
nextConfig: result.config,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (!opts.json) {
|
||||
logConfigUpdated(runtime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import path from "node:path";
|
|||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { identityHasValues, parseIdentityMarkdown } from "../agents/identity-file.js";
|
||||
import { DEFAULT_IDENTITY_FILENAME } from "../agents/workspace.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { IdentityConfig } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath, shortenHomePath } from "../utils.js";
|
||||
import { requireValidConfig } from "./agents.command-shared.js";
|
||||
import { requireValidConfigFileSnapshot } from "./agents.command-shared.js";
|
||||
import {
|
||||
type AgentIdentity,
|
||||
findAgentEntryIndex,
|
||||
|
|
@ -69,10 +69,12 @@ export async function agentsSetIdentityCommand(
|
|||
opts: AgentsSetIdentityOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
|
||||
if (!configSnapshot) {
|
||||
return;
|
||||
}
|
||||
const cfg = configSnapshot.sourceConfig ?? configSnapshot.config;
|
||||
const baseHash = configSnapshot.hash;
|
||||
|
||||
const agentRaw = coerceTrimmed(opts.agent);
|
||||
const nameRaw = coerceTrimmed(opts.name);
|
||||
|
|
@ -195,7 +197,10 @@ export async function agentsSetIdentityCommand(
|
|||
},
|
||||
};
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(runtime, {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/ind
|
|||
import { moveSingleAccountChannelSectionToDefaultAccount } from "../../channels/plugins/setup-helpers.js";
|
||||
import type { ChannelSetupPlugin } from "../../channels/plugins/setup-wizard-types.js";
|
||||
import type { ChannelId, ChannelPlugin, ChannelSetupInput } from "../../channels/plugins/types.js";
|
||||
import { writeConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { createClackPrompter } from "../../wizard/clack-prompter.js";
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from "../onboard-channels.js";
|
||||
import type { ChannelChoice } from "../onboard-types.js";
|
||||
import { applyAccountName, applyChannelAccountConfig } from "./add-mutators.js";
|
||||
import { channelLabel, requireValidConfig, shouldUseWizard } from "./shared.js";
|
||||
import { channelLabel, requireValidConfigFileSnapshot, shouldUseWizard } from "./shared.js";
|
||||
|
||||
export type ChannelsAddOptions = {
|
||||
channel?: string;
|
||||
|
|
@ -46,10 +46,12 @@ export async function channelsAddCommand(
|
|||
runtime: RuntimeEnv = defaultRuntime,
|
||||
params?: { hasFlags?: boolean },
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
|
||||
if (!configSnapshot) {
|
||||
return;
|
||||
}
|
||||
const cfg = (configSnapshot.sourceConfig ?? configSnapshot.config) as OpenClawConfig;
|
||||
const baseHash = configSnapshot.hash;
|
||||
let nextConfig = cfg;
|
||||
|
||||
const useWizard = shouldUseWizard(params);
|
||||
|
|
@ -177,7 +179,10 @@ export async function channelsAddCommand(
|
|||
}
|
||||
}
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
await runCollectedChannelOnboardingPostWriteHooks({
|
||||
hooks: postWriteHooks.drain(),
|
||||
cfg: nextConfig,
|
||||
|
|
@ -348,7 +353,10 @@ export async function channelsAddCommand(
|
|||
runtime,
|
||||
});
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
runtime.log(`Added ${channelLabel(channel)} account "${accountId}".`);
|
||||
const afterAccountConfigWritten = plugin.setup?.afterAccountConfigWritten;
|
||||
if (afterAccountConfigWritten) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import type {
|
|||
ChannelCapabilitiesDisplayLine,
|
||||
ChannelPlugin,
|
||||
} from "../../channels/plugins/types.js";
|
||||
import { writeConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
readConfigFileSnapshot,
|
||||
replaceConfigFile,
|
||||
type OpenClawConfig,
|
||||
} from "../../config/config.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
|
|
@ -207,6 +211,7 @@ export async function channelsCapabilitiesCommand(
|
|||
opts: ChannelsCapabilitiesOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
|
||||
const loadedCfg = await requireValidConfig(runtime);
|
||||
if (!loadedCfg) {
|
||||
return;
|
||||
|
|
@ -240,7 +245,10 @@ export async function channelsCapabilitiesCommand(
|
|||
});
|
||||
if (resolved.configChanged) {
|
||||
cfg = resolved.cfg;
|
||||
await writeConfigFile(cfg);
|
||||
await replaceConfigFile({
|
||||
nextConfig: cfg,
|
||||
baseHash: (await sourceSnapshotPromise)?.hash,
|
||||
});
|
||||
}
|
||||
return resolved.plugin ? [resolved.plugin] : null;
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@ import {
|
|||
listChannelPlugins,
|
||||
normalizeChannelId,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import { type OpenClawConfig, writeConfigFile } from "../../config/config.js";
|
||||
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { createClackPrompter } from "../../wizard/clack-prompter.js";
|
||||
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
|
||||
import { type ChatChannel, channelLabel, requireValidConfig, shouldUseWizard } from "./shared.js";
|
||||
import {
|
||||
type ChatChannel,
|
||||
channelLabel,
|
||||
requireValidConfigFileSnapshot,
|
||||
shouldUseWizard,
|
||||
} from "./shared.js";
|
||||
|
||||
export type ChannelsRemoveOptions = {
|
||||
channel?: string;
|
||||
|
|
@ -30,11 +35,12 @@ export async function channelsRemoveCommand(
|
|||
runtime: RuntimeEnv = defaultRuntime,
|
||||
params?: { hasFlags?: boolean },
|
||||
) {
|
||||
const loadedCfg = await requireValidConfig(runtime);
|
||||
if (!loadedCfg) {
|
||||
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
|
||||
if (!configSnapshot) {
|
||||
return;
|
||||
}
|
||||
let cfg = loadedCfg;
|
||||
const baseHash = configSnapshot.hash;
|
||||
let cfg = (configSnapshot.sourceConfig ?? configSnapshot.config) as OpenClawConfig;
|
||||
|
||||
const useWizard = shouldUseWizard(params);
|
||||
const prompter = useWizard ? createClackPrompter() : null;
|
||||
|
|
@ -160,7 +166,10 @@ export async function channelsRemoveCommand(
|
|||
});
|
||||
}
|
||||
|
||||
await writeConfigFile(next);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
if (useWizard && prompter) {
|
||||
await prompter.outro(
|
||||
deleteConfig
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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 { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.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";
|
||||
|
|
@ -72,6 +72,7 @@ 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({
|
||||
config: loadedRaw,
|
||||
|
|
@ -103,7 +104,10 @@ export async function channelsResolveCommand(opts: ChannelsResolveOptions, runti
|
|||
: null;
|
||||
if (resolvedExplicit?.configChanged) {
|
||||
cfg = resolvedExplicit.cfg;
|
||||
await writeConfigFile(cfg);
|
||||
await replaceConfigFile({
|
||||
nextConfig: cfg,
|
||||
baseHash: (await sourceSnapshotPromise)?.hash,
|
||||
});
|
||||
}
|
||||
|
||||
const selection = explicitChannel
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@ import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targ
|
|||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { requireValidConfigSnapshot } from "../config-validation.js";
|
||||
import {
|
||||
requireValidConfigFileSnapshot,
|
||||
requireValidConfigSnapshot,
|
||||
} from "../config-validation.js";
|
||||
|
||||
export type ChatChannel = ChannelId;
|
||||
|
||||
export { requireValidConfigSnapshot };
|
||||
export { requireValidConfigFileSnapshot };
|
||||
|
||||
export async function requireValidConfig(
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { type OpenClawConfig, readConfigFileSnapshot } from "../config/config.js";
|
||||
import {
|
||||
type ConfigFileSnapshot,
|
||||
type OpenClawConfig,
|
||||
readConfigFileSnapshot,
|
||||
} from "../config/config.js";
|
||||
import { formatConfigIssueLines } from "../config/issue-format.js";
|
||||
import {
|
||||
buildPluginCompatibilityNotices,
|
||||
|
|
@ -7,10 +11,10 @@ import {
|
|||
} from "../plugins/status.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
export async function requireValidConfigSnapshot(
|
||||
export async function requireValidConfigFileSnapshot(
|
||||
runtime: RuntimeEnv,
|
||||
opts?: { includeCompatibilityAdvisory?: boolean },
|
||||
): Promise<OpenClawConfig | null> {
|
||||
): Promise<ConfigFileSnapshot | null> {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
const issues =
|
||||
|
|
@ -23,7 +27,7 @@ export async function requireValidConfigSnapshot(
|
|||
return null;
|
||||
}
|
||||
if (opts?.includeCompatibilityAdvisory !== true) {
|
||||
return snapshot.config;
|
||||
return snapshot;
|
||||
}
|
||||
const compatibility = buildPluginCompatibilityNotices({ config: snapshot.config });
|
||||
if (compatibility.length > 0) {
|
||||
|
|
@ -38,5 +42,12 @@ export async function requireValidConfigSnapshot(
|
|||
].join("\n"),
|
||||
);
|
||||
}
|
||||
return snapshot.config;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export async function requireValidConfigSnapshot(
|
||||
runtime: RuntimeEnv,
|
||||
opts?: { includeCompatibilityAdvisory?: boolean },
|
||||
): Promise<OpenClawConfig | null> {
|
||||
return (await requireValidConfigFileSnapshot(runtime, opts))?.config ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import fsPromises from "node:fs/promises";
|
|||
import nodePath from "node:path";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { readConfigFileSnapshot, resolveGatewayPort, writeConfigFile } from "../config/config.js";
|
||||
import { readConfigFileSnapshot, replaceConfigFile, resolveGatewayPort } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
|
@ -237,6 +237,7 @@ export async function runConfigureWizard(
|
|||
const prompter = createClackPrompter();
|
||||
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
let currentBaseHash = snapshot.hash;
|
||||
const baseConfig: OpenClawConfig = snapshot.valid
|
||||
? (snapshot.sourceConfig ?? snapshot.config)
|
||||
: {};
|
||||
|
|
@ -323,7 +324,11 @@ export async function runConfigureWizard(
|
|||
command: opts.command,
|
||||
mode,
|
||||
});
|
||||
await writeConfigFile(remoteConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig: remoteConfig,
|
||||
...(currentBaseHash !== undefined ? { baseHash: currentBaseHash } : {}),
|
||||
});
|
||||
currentBaseHash = undefined;
|
||||
logConfigUpdated(runtime);
|
||||
outro("Remote gateway configured.");
|
||||
return;
|
||||
|
|
@ -352,7 +357,11 @@ export async function runConfigureWizard(
|
|||
command: opts.command,
|
||||
mode,
|
||||
});
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(currentBaseHash !== undefined ? { baseHash: currentBaseHash } : {}),
|
||||
});
|
||||
currentBaseHash = undefined;
|
||||
logConfigUpdated(runtime);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ export async function runNonInteractiveSetup(
|
|||
}
|
||||
|
||||
if (mode === "remote") {
|
||||
await runNonInteractiveRemoteSetup({ opts, runtime, baseConfig });
|
||||
await runNonInteractiveRemoteSetup({ opts, runtime, baseConfig, baseHash: snapshot.hash });
|
||||
return;
|
||||
}
|
||||
|
||||
await runNonInteractiveLocalSetup({ opts, runtime, baseConfig });
|
||||
await runNonInteractiveLocalSetup({ opts, runtime, baseConfig, baseHash: snapshot.hash });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resolveGatewayPort, writeConfigFile } from "../../config/config.js";
|
||||
import { replaceConfigFile, resolveGatewayPort } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { DEFAULT_GATEWAY_DAEMON_RUNTIME } from "../daemon-runtime.js";
|
||||
|
|
@ -71,8 +71,9 @@ export async function runNonInteractiveLocalSetup(params: {
|
|||
opts: OnboardOptions;
|
||||
runtime: RuntimeEnv;
|
||||
baseConfig: OpenClawConfig;
|
||||
baseHash?: string;
|
||||
}) {
|
||||
const { opts, runtime, baseConfig } = params;
|
||||
const { opts, runtime, baseConfig, baseHash } = params;
|
||||
const mode = "local" as const;
|
||||
|
||||
const workspaceDir = resolveNonInteractiveWorkspaceDir({
|
||||
|
|
@ -126,7 +127,10 @@ export async function runNonInteractiveLocalSetup(params: {
|
|||
nextConfig = applyNonInteractiveSkillsConfig({ nextConfig, opts, runtime });
|
||||
|
||||
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
logConfigUpdated(runtime);
|
||||
|
||||
await ensureWorkspaceAndSessions(workspaceDir, runtime, {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { writeConfigFile } from "../../config/config.js";
|
||||
import { replaceConfigFile } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { applyWizardMetadata } from "../onboard-helpers.js";
|
||||
|
|
@ -10,8 +10,9 @@ export async function runNonInteractiveRemoteSetup(params: {
|
|||
opts: OnboardOptions;
|
||||
runtime: RuntimeEnv;
|
||||
baseConfig: OpenClawConfig;
|
||||
baseHash?: string;
|
||||
}) {
|
||||
const { opts, runtime, baseConfig } = params;
|
||||
const { opts, runtime, baseConfig, baseHash } = params;
|
||||
const mode = "remote" as const;
|
||||
|
||||
const remoteUrl = opts.remoteUrl?.trim();
|
||||
|
|
@ -33,7 +34,10 @@ export async function runNonInteractiveRemoteSetup(params: {
|
|||
},
|
||||
};
|
||||
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
||||
await writeConfigFile(nextConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
...(baseHash !== undefined ? { baseHash } : {}),
|
||||
});
|
||||
logConfigUpdated(runtime);
|
||||
|
||||
const payload = {
|
||||
|
|
|
|||
|
|
@ -295,6 +295,7 @@ async function prepareGatewayStartupConfig(params: {
|
|||
authOverride: params.authOverride,
|
||||
tailscaleOverride: params.tailscaleOverride,
|
||||
persist: true,
|
||||
baseHash: params.configSnapshot.hash,
|
||||
});
|
||||
const runtimeStartupConfig = applyGatewayAuthOverridesForStartupPreflight(authBootstrap.cfg, {
|
||||
auth: params.authOverride,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type {
|
|||
GatewayTailscaleConfig,
|
||||
OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { assertExplicitGatewayAuthModeWhenBothConfigured } from "./auth-mode-policy.js";
|
||||
import { resolveGatewayAuth, type ResolvedGatewayAuth } from "./auth.js";
|
||||
|
|
@ -221,6 +221,7 @@ export async function ensureGatewayStartupAuth(params: {
|
|||
authOverride?: GatewayAuthConfig;
|
||||
tailscaleOverride?: GatewayTailscaleConfig;
|
||||
persist?: boolean;
|
||||
baseHash?: string;
|
||||
}): Promise<{
|
||||
cfg: OpenClawConfig;
|
||||
auth: ReturnType<typeof resolveGatewayAuth>;
|
||||
|
|
@ -270,7 +271,10 @@ export async function ensureGatewayStartupAuth(params: {
|
|||
resolvedAuth: resolved,
|
||||
});
|
||||
if (persist) {
|
||||
await writeConfigFile(nextCfg);
|
||||
await replaceConfigFile({
|
||||
nextConfig: nextCfg,
|
||||
baseHash: params.baseHash,
|
||||
});
|
||||
}
|
||||
|
||||
const nextAuth = resolveGatewayAuthFromConfig({
|
||||
|
|
|
|||
Loading…
Reference in New Issue