import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "../plugins/manifest-registry.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import { createCliRuntimeCapture, mockRuntimeModule } from "./test-runtime-capture.js";

/**
 * Test for issue #6070:
 * `openclaw config set/unset` must update snapshot.resolved (user config after $include/${ENV},
 * but before runtime defaults), so runtime defaults don't leak into the written config.
 */

const mockReadConfigFileSnapshot = vi.fn<() => Promise<ConfigFileSnapshot>>();
const mockWriteConfigFile = vi.fn<
  (
    cfg: OpenClawConfig,
    options?: { unsetPaths?: string[][]; explicitSetPaths?: string[][] },
  ) => Promise<void>
>(async () => {});
const mockResolveSecretRefValue = vi.fn();
const mockReadBestEffortRuntimeConfigSchema = vi.fn();
const mockLoadPluginMetadataSnapshot = vi.fn((configForTest: unknown) =>
  createPluginMetadataSnapshot(),
);

vi.mock("../config/config.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../config/config.js")>();
  return {
    ...actual,
    readConfigFileSnapshot: () => mockReadConfigFileSnapshot(),
    writeConfigFile: (
      cfg: OpenClawConfig,
      options?: { unsetPaths?: string[][]; explicitSetPaths?: string[][] },
    ) => mockWriteConfigFile(cfg, options),
    replaceConfigFile: (params: {
      nextConfig: OpenClawConfig;
      writeOptions?: { unsetPaths?: string[][]; explicitSetPaths?: string[][] };
    }) => mockWriteConfigFile(params.nextConfig, params.writeOptions),
  };
});

vi.mock("../secrets/resolve.js", () => ({
  resolveSecretRefValue: (...args: unknown[]) => mockResolveSecretRefValue(...args),
}));

vi.mock("../config/runtime-schema.js", () => ({
  readBestEffortRuntimeConfigSchema: () => mockReadBestEffortRuntimeConfigSchema(),
}));

vi.mock("../plugins/plugin-metadata-snapshot.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../plugins/plugin-metadata-snapshot.js")>();
  return {
    ...actual,
    loadPluginMetadataSnapshot: (config: unknown) => mockLoadPluginMetadataSnapshot(config),
    resolvePluginMetadataSnapshot: (params: { config?: unknown }) =>
      mockLoadPluginMetadataSnapshot(params.config),
  };
});

const { defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const mockLog = defaultRuntime.log;
const mockError = defaultRuntime.error;
const mockExit = defaultRuntime.exit;

vi.mock("../runtime.js", async () => {
  return mockRuntimeModule(
    () => vi.importActual<typeof import("../runtime.js")>("../runtime.js"),
    defaultRuntime,
  );
});

function buildSnapshot(params: {
  resolved: OpenClawConfig;
  config: OpenClawConfig;
}): ConfigFileSnapshot {
  return {
    path: "/tmp/openclaw.json",
    exists: true,
    raw: JSON.stringify(params.resolved),
    parsed: params.resolved,
    sourceConfig: params.resolved,
    resolved: params.resolved,
    valid: true,
    runtimeConfig: params.config,
    config: params.config,
    issues: [],
    warnings: [],
    legacyIssues: [],
  };
}

function setSnapshot(resolved: OpenClawConfig, config: OpenClawConfig) {
  mockReadConfigFileSnapshot.mockResolvedValueOnce(buildSnapshot({ resolved, config }));
}

function setSnapshotOnce(snapshot: ConfigFileSnapshot) {
  mockReadConfigFileSnapshot.mockResolvedValueOnce(snapshot);
}

function writeTempJson5File(prefix: string, value: unknown): string {
  const pathname = path.join(
    os.tmpdir(),
    `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
  );
  fs.writeFileSync(pathname, JSON.stringify(value), "utf8");
  return pathname;
}

function withRuntimeDefaults(resolved: OpenClawConfig): OpenClawConfig {
  return {
    ...resolved,
    agents: {
      ...resolved.agents,
      defaults: {
        model: "gpt-5.4",
      } as never,
    } as never,
  };
}

function createPluginManifestRecord(
  overrides: Partial<PluginManifestRecord> & Pick<PluginManifestRecord, "id">,
): PluginManifestRecord {
  return {
    channels: [],
    cliBackends: [],
    hooks: [],
    manifestPath: `/tmp/${overrides.id}/openclaw.plugin.json`,
    origin: "bundled",
    providers: [],
    rootDir: `/tmp/${overrides.id}`,
    skills: [],
    source: `/tmp/${overrides.id}/index.js`,
    ...overrides,
  };
}

function createPluginMetadataSnapshot(
  manifestRegistry: PluginManifestRegistry = { diagnostics: [], plugins: [] },
): PluginMetadataSnapshot {
  const plugins = manifestRegistry.plugins;
  return {
    policyHash: "test-policy",
    index: {
      version: 1,
      hostContractVersion: "test",
      compatRegistryVersion: "test",
      migrationVersion: 1,
      policyHash: "test-policy",
      generatedAtMs: 0,
      installRecords: {},
      plugins: [],
      diagnostics: [],
    },
    registryDiagnostics: [],
    manifestRegistry,
    plugins,
    diagnostics: manifestRegistry.diagnostics,
    byPluginId: new Map(plugins.map((plugin) => [plugin.id, plugin])),
    normalizePluginId: (pluginId: string) => pluginId.trim().toLowerCase(),
    owners: {
      channels: new Map(),
      channelConfigs: new Map(),
      providers: new Map(),
      modelCatalogProviders: new Map(),
      cliBackends: new Map(),
      setupProviders: new Map(),
      commandAliases: new Map(),
      contracts: new Map(),
    },
    metrics: {
      registrySnapshotMs: 0,
      manifestRegistryMs: 0,
      ownerMapsMs: 0,
      totalMs: 0,
      indexPluginCount: 0,
      manifestPluginCount: plugins.length,
    },
  };
}

function configRecordWithRequireMentionSchema() {
  return {
    type: "object",
    additionalProperties: {
      type: "object",
      properties: {
        requireMention: { type: "boolean" },
      },
    },
  };
}

function configChannelSchemaWithRecord(recordKey: string) {
  return {
    type: "object",
    properties: {
      [recordKey]: configRecordWithRequireMentionSchema(),
    },
  };
}

function setConfigMutationShapeSchema() {
  mockReadBestEffortRuntimeConfigSchema.mockResolvedValue({
    schema: {
      $schema: "http://json-schema.org/draft-07/schema#",
      type: "object",
      properties: {
        agents: {
          type: "object",
          properties: {
            list: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  id: { type: "string" },
                  name: { type: "string" },
                },
              },
            },
          },
        },
        channels: {
          type: "object",
          properties: {
            discord: configChannelSchemaWithRecord("guilds"),
            telegram: configChannelSchemaWithRecord("groups"),
          },
        },
      },
    },
    uiHints: {},
    version: "test",
    generatedAt: "2026-03-25T00:00:00.000Z",
  });
}

function setExternalFeishuSchema() {
  mockLoadPluginMetadataSnapshot.mockReturnValue(
    createPluginMetadataSnapshot({
      diagnostics: [],
      plugins: [
        createPluginManifestRecord({
          id: "openclaw-lark",
          origin: "global",
          channels: ["feishu"],
          channelConfigs: {
            feishu: {
              schema: {
                type: "object",
                properties: {
                  appId: { type: "string" },
                  appSecret: { type: "string" },
                  replyMode: { type: "string", enum: ["thread", "direct"] },
                  footer: { type: "string" },
                },
                required: ["appId", "appSecret"],
                additionalProperties: false,
              },
              uiHints: {},
            },
          },
        }),
      ],
    }),
  );
}

function makeInvalidSnapshot(params: {
  issues: ConfigFileSnapshot["issues"];
  warnings?: ConfigFileSnapshot["warnings"];
  path?: string;
}): ConfigFileSnapshot {
  return {
    path: params.path ?? "/tmp/custom-openclaw.json",
    exists: true,
    raw: "{}",
    parsed: {},
    sourceConfig: {},
    resolved: {},
    valid: false,
    runtimeConfig: {},
    config: {},
    issues: params.issues,
    warnings: params.warnings ?? [],
    legacyIssues: [],
  };
}

function firstMockArg(mock: { mock: { calls: ReadonlyArray<ReadonlyArray<unknown>> } }): unknown {
  const call = mock.mock.calls[0];
  if (!call) {
    throw new Error("expected mock to have at least one call");
  }
  return call[0];
}

function lastMockArg(mock: { mock: { calls: ReadonlyArray<ReadonlyArray<unknown>> } }): unknown {
  const calls = mock.mock.calls;
  const call = calls[calls.length - 1];
  if (!call) {
    throw new Error("expected mock to have at least one call");
  }
  return call[0];
}

function parseLastLogPayload(): unknown {
  const raw = lastMockArg(mockLog);
  expect(typeof raw).toBe("string");
  return JSON.parse(String(raw)) as unknown;
}

async function runValidateJsonAndGetPayload() {
  await expect(runConfigCommand(["config", "validate", "--json"])).rejects.toThrow("__exit__:1");
  const raw = firstMockArg(mockLog);
  expect(typeof raw).toBe("string");
  return JSON.parse(String(raw)) as {
    valid: boolean;
    path: string;
    issues: Array<{
      path: string;
      message: string;
      allowedValues?: string[];
      allowedValuesHiddenCount?: number;
    }>;
  };
}

function firstWrittenConfig(): OpenClawConfig {
  const written = firstMockArg(mockWriteConfigFile);
  if (!written) {
    throw new Error("expected written config");
  }
  return written as OpenClawConfig;
}

function firstWriteConfigOptions():
  | { unsetPaths?: string[][]; explicitSetPaths?: string[][] }
  | undefined {
  return mockWriteConfigFile.mock.calls[0]?.[1];
}

function requireWriteOptions(): { unsetPaths?: string[][]; explicitSetPaths?: string[][] } {
  const options = firstWriteConfigOptions();
  if (!options) {
    throw new Error("expected write options");
  }
  return options;
}

function expectLogIncludes(text: string) {
  expect(mockLog.mock.calls.map((call) => String(call[0])).join("\n")).toContain(text);
}

function expectLogExcludes(text: string) {
  expect(mockLog.mock.calls.map((call) => String(call[0])).join("\n")).not.toContain(text);
}

function expectErrorIncludes(text: string) {
  expect(mockError.mock.calls.map((call) => String(call[0])).join("\n")).toContain(text);
}

function requireRecord(value: unknown, label: string): Record<string, unknown> {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`expected ${label} to be an object`);
  }
  return value as Record<string, unknown>;
}

function requireResolveSecretRefCall(index: number): [unknown, unknown] {
  const call = mockResolveSecretRefValue.mock.calls[index];
  if (!call) {
    throw new Error(`expected SecretRef resolver call ${index}`);
  }
  return call as [unknown, unknown];
}

let registerConfigCli: typeof import("./config-cli.js").registerConfigCli;
let sharedProgram: Command;

async function runConfigCommand(args: string[]) {
  await sharedProgram.parseAsync(args, { from: "user" });
}

describe("config cli", () => {
  beforeAll(async () => {
    ({ registerConfigCli } = await import("./config-cli.js"));
    sharedProgram = new Command();
    sharedProgram.exitOverride();
    registerConfigCli(sharedProgram);
  });

  beforeEach(() => {
    vi.clearAllMocks();
    resetRuntimeCapture();
    mockLoadPluginMetadataSnapshot.mockReturnValue(createPluginMetadataSnapshot());
    mockReadBestEffortRuntimeConfigSchema.mockResolvedValue({
      schema: {
        $schema: "http://json-schema.org/draft-07/schema#",
        type: "object",
        properties: {
          channels: {
            type: "object",
            properties: {
              telegram: {
                type: "object",
                properties: {
                  token: { type: "string" },
                },
              },
            },
          },
          plugins: {
            type: "object",
            properties: {
              entries: {
                type: "object",
              },
            },
          },
        },
      },
      uiHints: {},
      version: "test",
      generatedAt: "2026-03-25T00:00:00.000Z",
    });
    mockExit.mockImplementation((code: number) => {
      const errorMessages = mockError.mock.calls.map((call) => call.join(" ")).join("; ");
      throw new Error(`__exit__:${code} - ${errorMessages}`);
    });
    mockResolveSecretRefValue.mockResolvedValue("resolved-secret");
  });

  describe("config set - issue #6070", () => {
    it("preserves existing config keys when setting a new value", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          list: [{ id: "main" }, { id: "oracle", workspace: "~/oracle-workspace" }],
        },
        gateway: { port: 18789 },
        tools: { allow: ["group:fs"] },
        logging: { level: "debug" },
      };
      const runtimeMerged: OpenClawConfig = {
        ...withRuntimeDefaults(resolved),
      };
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand(["config", "set", "gateway.auth.mode", "token"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({ mode: "token" });
      expect(written.gateway?.port).toBe(18789);
      expect(written.agents).toEqual(resolved.agents);
      expect(written.tools).toEqual(resolved.tools);
      expect(written.logging).toEqual(resolved.logging);
      expect(written.agents).not.toHaveProperty("defaults");
    });

    it("marks set paths explicit so default-equal writes persist", async () => {
      const resolved: OpenClawConfig = {
        channels: {
          telegram: {
            botToken: "tok-abc",
          },
        },
      };
      const runtimeMerged = {
        ...resolved,
        channels: {
          telegram: {
            botToken: "tok-abc",
            dmPolicy: "pairing",
          },
        },
      } as OpenClawConfig;
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand(["config", "set", "channels.telegram.dmPolicy", "pairing"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      expect(requireWriteOptions().explicitSetPaths).toEqual([
        ["channels", "telegram", "dmPolicy"],
      ]);
    });

    it("marks object set paths explicit so nested default-equal writes persist", async () => {
      const resolved: OpenClawConfig = {
        channels: {
          telegram: {
            botToken: "tok-abc",
          },
        },
      };
      const runtimeMerged = {
        ...resolved,
        channels: {
          telegram: {
            botToken: "tok-abc",
            dmPolicy: "pairing",
          },
        },
      } as OpenClawConfig;
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand([
        "config",
        "set",
        "channels.telegram",
        '{"botToken":"tok-abc","dmPolicy":"pairing"}',
        "--strict-json",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      expect(requireWriteOptions().explicitSetPaths).toEqual([["channels", "telegram"]]);
    });

    it("does not inject runtime defaults into the written config", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      const runtimeMerged = {
        ...resolved,
        agents: {
          defaults: {
            model: "gpt-5.4",
            contextWindow: 128_000,
            maxTokens: 16_000,
          },
        } as never,
        messages: { ackReaction: "✅" } as never,
        sessions: { persistence: { enabled: true } } as never,
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand(["config", "set", "gateway.auth.mode", "token"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written).not.toHaveProperty("agents.defaults.model");
      expect(written).not.toHaveProperty("agents.defaults.contextWindow");
      expect(written).not.toHaveProperty("agents.defaults.maxTokens");
      expect(written).not.toHaveProperty("messages.ackReaction");
      expect(written).not.toHaveProperty("sessions.persistence");
      expect(written.gateway?.port).toBe(18789);
      expect(written.gateway?.auth).toEqual({ mode: "token" });
    });

    it("writes agents.defaults.videoGenerationModel.primary without disturbing sibling defaults", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            model: "openai/gpt-5.4",
            imageGenerationModel: {
              primary: "openai/gpt-image-1",
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "agents.defaults.videoGenerationModel.primary",
        "qwen/wan2.6-t2v",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.defaults?.model).toBe("openai/gpt-5.4");
      expect(written.agents?.defaults?.imageGenerationModel).toEqual({
        primary: "openai/gpt-image-1",
      });
      expect(written.agents?.defaults?.videoGenerationModel).toEqual({
        primary: "qwen/wan2.6-t2v",
      });
    });

    it("normalizes retired Google Gemini model refs before writing config mutations", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            model: {
              fallbacks: ["google/gemini-3-pro-preview"],
            },
            models: {
              "google/gemini-3-pro-preview": { alias: "gemini" },
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "agents.defaults.model.primary",
        "google/gemini-3-pro-preview",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.defaults?.model).toEqual({
        primary: "google/gemini-3.1-pro-preview",
        fallbacks: ["google/gemini-3.1-pro-preview"],
      });
      expect(written.agents?.defaults?.models).toEqual({
        "google/gemini-3.1-pro-preview": { alias: "gemini" },
      });
    });

    it("normalizes explicit model-map paths before writing config mutations", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            models: {
              "google/gemini-3-pro-preview": {},
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "agents.defaults.models.google/gemini-3-pro-preview.alias",
        "gemini",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.defaults?.models).toEqual({
        "google/gemini-3.1-pro-preview": { alias: "gemini" },
      });
      expect(requireWriteOptions().explicitSetPaths).toEqual([
        ["agents", "defaults", "models", "google/gemini-3.1-pro-preview", "alias"],
      ]);
    });

    it("normalizes agent-list model refs before writing config mutations", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          list: [
            {
              id: "tester",
              model: { primary: "google/gemini-3-pro-preview" },
              models: {
                "google/gemini-3-pro-preview": { alias: "gemini" },
              },
            },
          ],
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.port", "18790"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const agent = firstWrittenConfig().agents?.list?.[0];
      expect(agent?.model).toEqual({ primary: "google/gemini-3.1-pro-preview" });
      expect(agent?.models).toEqual({
        "google/gemini-3.1-pro-preview": { alias: "gemini" },
      });
    });

    it("normalizes provider catalog model refs before writing config mutations", async () => {
      const resolved: OpenClawConfig = {
        models: {
          providers: {
            google: {
              api: "google-generative-ai",
              baseUrl: "https://generativelanguage.googleapis.com/v1beta",
              models: [
                {
                  id: "google/gemini-3-pro-preview",
                  name: "Gemini 3 Pro",
                  contextWindow: 1_048_576,
                  maxTokens: 65_536,
                  input: ["text", "image"],
                  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
                  reasoning: true,
                },
              ],
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.port", "18790"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      expect(firstWrittenConfig().models?.providers?.google?.models?.[0]?.id).toBe(
        "google/gemini-3.1-pro-preview",
      );
    });

    it("rejects plugin install record config updates", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          'plugins.installs["openclaw-web-search"].spec',
          '"@ollama/openclaw-web-search@0.2.2"',
          "--strict-json",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("openclaw plugins install <spec>");
      expectErrorIncludes("openclaw plugins update <plugin-id>");
    });

    it("rejects auto-managed meta.lastTouchedVersion config updates (#80849)", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "meta.lastTouchedVersion",
          "BOGUS-NOT-A-VERSION",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedVersion");
      expectErrorIncludes("auto-managed");
    });

    it("rejects auto-managed meta.lastTouchedAt config updates (#80849)", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "meta.lastTouchedAt",
          "1999-01-01T00:00:00.000Z",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedAt");
      expectErrorIncludes("auto-managed");
    });

    it("rejects auto-managed meta paths via config unset (#80849)", async () => {
      await expect(runConfigCommand(["config", "unset", "meta.lastTouchedAt"])).rejects.toThrow(
        "__exit__:1",
      );

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedAt");
      expectErrorIncludes("auto-managed");
    });

    it("rejects parent meta path mutations when payload merges an auto-managed child (#80849)", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "meta",
          '{"lastTouchedVersion":"BOGUS-NOT-A-VERSION"}',
          "--strict-json",
          "--merge",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedVersion");
      expectErrorIncludes("auto-managed");
    });

    it("rejects parent meta path replacement that would clobber auto-managed children (#80849)", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "meta",
          '{"lastTouchedAt":"1999-01-01T00:00:00.000Z"}',
          "--strict-json",
          "--replace",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedAt");
      expectErrorIncludes("auto-managed");
    });

    it("rejects config unset meta because deleting the parent removes auto-managed children (#80849)", async () => {
      await expect(runConfigCommand(["config", "unset", "meta"])).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("meta.lastTouchedVersion");
      expectErrorIncludes("auto-managed");
    });

    it("does not auto-managed-reject parent meta merges that leave the managed children alone (#80849)", async () => {
      // The merge payload only references a non-auto-managed key; the auto-managed
      // guard MUST NOT fire — otherwise a future schema-valid sibling of
      // meta.lastTouched* would be collateral-rejected. Downstream layers (schema
      // validator, etc.) may still legitimately reject this; we only care that the
      // rejection was NOT from our auto-managed guard.
      setSnapshot({}, {});
      try {
        await runConfigCommand([
          "config",
          "set",
          "meta",
          '{"unrelated":"x"}',
          "--strict-json",
          "--merge",
          "--dry-run",
        ]);
      } catch {
        // Tolerated: any downstream rejection. Inspected below.
      }
      const errorMessages = mockError.mock.calls.map((call) => String(call[0])).join("\n");
      expect(errorMessages).not.toContain("auto-managed");
    });

    it("rejects protected model map replacement unless explicitly requested", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.4": { alias: "GPT" },
              "anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "agents.defaults.models",
          '{"openai/gpt-5.4":{}}',
          "--strict-json",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("Refusing to replace agents.defaults.models");
    });

    it("merges protected model map values with --merge", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.4": { alias: "GPT" },
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "agents.defaults.models",
        '{"anthropic/claude-sonnet-4-6":{"alias":"Sonnet"}}',
        "--strict-json",
        "--merge",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.defaults?.models).toEqual({
        "openai/gpt-5.4": { alias: "GPT" },
        "anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
      });
    });

    it("merges provider model arrays by id with --merge", async () => {
      const resolved = {
        models: {
          providers: {
            ollama: {
              api: "ollama",
              models: [
                { id: "llama3.2", name: "Llama 3.2", contextWindow: 131072 },
                { id: "qwen3", name: "Qwen 3" },
              ],
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "models.providers.ollama.models",
        '[{"id":"llama3.2","name":"Llama 3.2 latest"},{"id":"gemma4","name":"Gemma 4"}]',
        "--strict-json",
        "--merge",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.models?.providers?.ollama?.models).toEqual([
        { id: "llama3.2", name: "Llama 3.2 latest", contextWindow: 131072 },
        { id: "qwen3", name: "Qwen 3" },
        { id: "gemma4", name: "Gemma 4" },
      ]);
    });

    it("drops gateway.auth.password when switching mode to token", async () => {
      const resolved: OpenClawConfig = {
        gateway: {
          auth: {
            mode: "password",
            token: "token-keep",
            password: "password-drop", // pragma: allowlist secret
            allowTailscale: true,
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.auth.mode", "token"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({
        mode: "token",
        token: "token-keep",
        allowTailscale: true,
      });
      expectLogIncludes("Removed inactive gateway.auth.password for gateway.auth.mode=token");
    });

    it("drops gateway.auth.token when switching mode to password", async () => {
      const resolved: OpenClawConfig = {
        gateway: {
          auth: {
            mode: "token",
            token: "token-drop",
            password: "password-keep", // pragma: allowlist secret
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.auth.mode", "password"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({
        mode: "password",
        password: "password-keep", // pragma: allowlist secret
      });
      expectLogIncludes("Removed inactive gateway.auth.token for gateway.auth.mode=password");
    });

    it("applies mode-based credential cleanup using the final batch result", async () => {
      const resolved: OpenClawConfig = {
        gateway: {
          auth: {
            mode: "password",
            token: "token-keep",
            password: "password-drop", // pragma: allowlist secret
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "--batch-json",
        '[{"path":"gateway.auth.password","value":"password-updated"},{"path":"gateway.auth.mode","value":"token"}]',
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({
        mode: "token",
        token: "token-keep",
      });
      expectLogIncludes("Removed inactive gateway.auth.password for gateway.auth.mode=token");
    });
  });

  describe("config get", () => {
    it("redacts sensitive values", async () => {
      const resolved: OpenClawConfig = {
        gateway: {
          auth: {
            token: "super-secret-token",
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "get", "gateway.auth.token"]);

      expect(mockLog).toHaveBeenCalledWith("__OPENCLAW_REDACTED__");
    });

    it("prints materialized subagent archive default", async () => {
      const resolved: OpenClawConfig = {};
      const config: OpenClawConfig = {
        agents: {
          defaults: {
            maxConcurrent: 4,
            subagents: {
              maxConcurrent: 8,
              archiveAfterMinutes: 60,
            },
          },
        },
      };
      setSnapshot(resolved, config);

      await runConfigCommand(["config", "get", "agents.defaults.subagents.archiveAfterMinutes"]);

      expect(mockLog).toHaveBeenCalledWith("60");
    });
  });

  describe("config validate", () => {
    it("prints success and exits 0 when config is valid", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "validate"]);

      expect(mockExit).not.toHaveBeenCalled();
      expect(mockError).not.toHaveBeenCalled();
      expectLogIncludes("Config valid:");
    });

    it("prints issues and exits 1 when config is invalid", async () => {
      setSnapshotOnce(
        makeInvalidSnapshot({
          issues: [
            {
              path: "agents.defaults.suppressToolErrorWarnings",
              message: "Unrecognized key(s) in object",
            },
          ],
        }),
      );

      await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");

      expectErrorIncludes("config is invalid");
      expectErrorIncludes("agents.defaults.suppressToolErrorWarnings");
      expect(mockLog).not.toHaveBeenCalled();
    });

    it("replaces doctor advice for plugin packaging compiled-output failures", async () => {
      setSnapshotOnce(
        makeInvalidSnapshot({
          issues: [
            {
              path: "plugins.slots.memory",
              message: "plugin not found: source-only-pack",
            },
          ],
          warnings: [
            {
              path: "plugins",
              message:
                "plugin source-only-pack: installed plugin package requires compiled runtime output for TypeScript entry index.ts: expected ./dist/index.js. This is a plugin packaging issue, not a local config problem.",
            },
          ],
        }),
      );

      await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");

      expectErrorIncludes("plugin not found: source-only-pack");
      expectErrorIncludes("This is a plugin packaging issue, not a local config problem.");
      expectErrorIncludes("disable/uninstall the plugin");
      expect(mockError.mock.calls.map((call) => String(call[0])).join("\n")).not.toContain(
        "openclaw doctor --fix",
      );
      expect(mockLog).not.toHaveBeenCalled();
    });

    it("returns machine-readable JSON with --json for invalid config", async () => {
      setSnapshotOnce(
        makeInvalidSnapshot({
          issues: [{ path: "gateway.bind", message: "Invalid enum value" }],
        }),
      );

      const payload = await runValidateJsonAndGetPayload();
      expect(payload.valid).toBe(false);
      expect(payload.path).toBe("/tmp/custom-openclaw.json");
      expect(payload.issues).toEqual([{ path: "gateway.bind", message: "Invalid enum value" }]);
      expect(mockError).not.toHaveBeenCalled();
    });

    it("preserves allowed-values metadata in --json output", async () => {
      setSnapshotOnce(
        makeInvalidSnapshot({
          issues: [
            {
              path: "update.channel",
              message: 'Invalid input (allowed: "stable", "beta", "dev")',
              allowedValues: ["stable", "beta", "dev"],
              allowedValuesHiddenCount: 0,
            },
          ],
        }),
      );

      const payload = await runValidateJsonAndGetPayload();
      expect(payload.valid).toBe(false);
      expect(payload.path).toBe("/tmp/custom-openclaw.json");
      expect(payload.issues).toEqual([
        {
          path: "update.channel",
          message: 'Invalid input (allowed: "stable", "beta", "dev")',
          allowedValues: ["stable", "beta", "dev"],
        },
      ]);
      expect(mockError).not.toHaveBeenCalled();
    });

    it("prints file-not-found and exits 1 when config file is missing", async () => {
      setSnapshotOnce({
        path: "/tmp/openclaw.json",
        exists: false,
        raw: null,
        parsed: {},
        resolved: {},
        sourceConfig: {},
        valid: true,
        config: {},
        runtimeConfig: {},
        issues: [],
        warnings: [],
        legacyIssues: [],
      });

      await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");
      expectErrorIncludes("Config file not found:");
      expect(mockLog).not.toHaveBeenCalled();
    });
  });

  describe("config schema", () => {
    it("prints the generated JSON schema as plain text", async () => {
      const { computeBaseConfigSchemaResponse } = await import("../config/schema-base.js");
      mockReadBestEffortRuntimeConfigSchema.mockResolvedValueOnce(
        computeBaseConfigSchemaResponse({
          generatedAt: "2026-03-25T00:00:00.000Z",
        }),
      );

      await runConfigCommand(["config", "schema"]);

      expect(mockExit).not.toHaveBeenCalled();
      expect(mockError).not.toHaveBeenCalled();
      expect(defaultRuntime.writeJson).toHaveBeenCalledTimes(1);
      const payload = parseLastLogPayload() as {
        properties?: Record<string, unknown>;
      };
      const gateway = payload.properties?.gateway as
        | { properties?: Record<string, unknown> }
        | undefined;
      const gatewayPort = gateway?.properties?.port as
        | { title?: string; description?: string }
        | undefined;
      expect(payload.properties?.$schema).toEqual({ type: "string" });
      expect(gatewayPort?.title).toBe("Gateway Port");
      expect(gatewayPort?.description).toContain("TCP port used by the gateway listener");
      const channels = requireRecord(payload.properties?.channels, "schema channels");
      expect(channels.title).toBe("Channels");
      expect(channels.properties).toEqual({});
      expect(channels.additionalProperties).toBe(true);
      const plugins = requireRecord(payload.properties?.plugins, "schema plugins");
      expect(plugins.title).toBe("Plugins");
      expect(plugins.description).toContain("Plugin system controls");
      const pluginProperties = requireRecord(plugins.properties, "schema plugin properties");
      expect(requireRecord(pluginProperties.entries, "schema plugin entries").title).toBe(
        "Plugin Entries",
      );
    });

    it("falls back cleanly when best-effort schema loading returns channel-only data", async () => {
      mockReadBestEffortRuntimeConfigSchema.mockResolvedValueOnce({
        schema: {
          $schema: "http://json-schema.org/draft-07/schema#",
          type: "object",
          properties: {
            channels: {
              type: "object",
              properties: {
                telegram: {
                  type: "object",
                },
              },
            },
          },
        },
        uiHints: {},
        version: "test",
        generatedAt: "2026-03-25T00:00:00.000Z",
      });

      await runConfigCommand(["config", "schema"]);

      expect(defaultRuntime.writeJson).toHaveBeenCalledTimes(1);
      const payload = parseLastLogPayload() as {
        properties?: Record<string, unknown>;
      };
      expect(payload.properties?.$schema).toEqual({ type: "string" });
      const channels = requireRecord(payload.properties?.channels, "schema channels");
      expect(channels.type).toBe("object");
      expect(channels.properties).toEqual({ telegram: { type: "object" } });
      expect(payload.properties?.plugins).toBeUndefined();
      expect(mockError).not.toHaveBeenCalled();
    });
  });

  describe("config set parsing flags", () => {
    it("falls back to raw string when parsing fails and strict mode is off", async () => {
      const resolved: OpenClawConfig = { gateway: { port: 18789 } };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.auth.mode", "{bad"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({ mode: "{bad" });
    });

    it("throws when strict parsing is enabled via --strict-json", async () => {
      await expect(
        runConfigCommand(["config", "set", "gateway.auth.mode", "{bad", "--strict-json"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expectErrorIncludes('Could not parse "{bad" as JSON for --strict-json.');
      expectErrorIncludes("For plain strings, omit --strict-json.");
    });

    it("keeps --json as a strict parsing alias", async () => {
      await expect(
        runConfigCommand(["config", "set", "gateway.auth.mode", "{bad", "--json"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
    });

    it("rejects JSON5-only object syntax when strict parsing is enabled", async () => {
      await expect(
        runConfigCommand(["config", "set", "gateway.auth", "{mode:'token'}", "--strict-json"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
    });

    it("accepts --strict-json with batch mode and applies batch payload", async () => {
      const resolved: OpenClawConfig = { gateway: { port: 18789 } };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "--batch-json",
        '[{"path":"gateway.auth.mode","value":"token"}]',
        "--strict-json",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({ mode: "token" });
    });

    it("shows --strict-json and keeps --json as a legacy alias in help", () => {
      const program = new Command();
      registerConfigCli(program);

      const configCommand = program.commands.find((command) => command.name() === "config");
      const setCommand = configCommand?.commands.find((command) => command.name() === "set");
      const helpText = setCommand?.helpInformation() ?? "";
      const configHelpText = configCommand?.helpInformation() ?? "";

      expect(configHelpText).toContain("get/set/patch/unset/file/schema/validate");
      expect(configHelpText).not.toContain("get/set/apply/unset/file/schema/validate");
      expect(helpText).toContain("--strict-json");
      expect(helpText).toContain("--json");
      expect(helpText).toContain("Legacy alias for --strict-json");
      expect(helpText).toContain("Value (JSON/JSON5 or raw string)");
      expect(helpText).toContain("Strict JSON parsing (error instead of");
      expect(helpText).toContain("--ref-provider");
      expect(helpText).toContain("--provider-source");
      expect(helpText).toContain("--batch-json");
      expect(helpText).toContain("--dry-run");
      expect(helpText).toContain("--allow-exec");
      // Ignore Commander line wrapping and env-injected CLI prefixes.
      const normalizedHelp = helpText.replace(/\s+/g, " ");
      expect(normalizedHelp).toContain("config set gateway.port 19001 --strict-json");
      expect(normalizedHelp).toContain(
        "channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN",
      );
      expect(normalizedHelp).toContain("--batch-file ./config-set.batch.json --dry-run");
    });
  });

  describe("config set builders and dry-run", () => {
    it("supports SecretRef builder mode without requiring a value argument", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "default",
        "--ref-source",
        "env",
        "--ref-id",
        "DISCORD_BOT_TOKEN",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.channels?.discord?.token).toEqual({
        source: "env",
        provider: "default",
        id: "DISCORD_BOT_TOKEN",
      });
    });

    it("keeps numeric config set path segments as object keys for schema-backed Discord guild records", async () => {
      setConfigMutationShapeSchema();
      const resolved: OpenClawConfig = {
        channels: {
          discord: {
            enabled: true,
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.guilds.1495587801394184362.requireMention",
        "true",
        "--strict-json",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as {
        channels?: { discord?: { guilds?: unknown } };
      };
      expect(written.channels?.discord?.guilds).toEqual({
        "1495587801394184362": {
          requireMention: true,
        },
      });
      expect(Array.isArray(written.channels?.discord?.guilds)).toBe(false);
    });

    it("keeps numeric config set path segments as object keys for other schema-backed records", async () => {
      setConfigMutationShapeSchema();
      const resolved: OpenClawConfig = {
        channels: {
          telegram: {
            enabled: true,
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.telegram.groups.1495587801394184362.requireMention",
        "true",
        "--strict-json",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as {
        channels?: { telegram?: { groups?: unknown } };
      };
      expect(written.channels?.telegram?.groups).toEqual({
        "1495587801394184362": {
          requireMention: true,
        },
      });
      expect(Array.isArray(written.channels?.telegram?.groups)).toBe(false);
    });

    it("still creates arrays for schema-backed numeric list indexes", async () => {
      setConfigMutationShapeSchema();
      const resolved: OpenClawConfig = {};
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "agents.list.0.id", '"tech"', "--strict-json"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as {
        agents?: { list?: unknown };
      };
      expect(written.agents?.list).toEqual([{ id: "tech" }]);
      expect(Array.isArray(written.agents?.list)).toBe(true);
    });

    it("fails early when unsupported mutable paths are assigned SecretRef objects (builder mode)", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "hooks.token",
          "--ref-provider",
          "default",
          "--ref-source",
          "env",
          "--ref-id",
          "HOOK_TOKEN",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("Config policy validation failed: unsupported SecretRef usage");
      expectErrorIncludes("hooks.token");
    });

    it("fails early when parent-object writes include unsupported SecretRef objects", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "hooks",
          '{"token":{"source":"env","provider":"default","id":"HOOK_TOKEN"}}',
          "--strict-json",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("Config policy validation failed: unsupported SecretRef usage");
      expectErrorIncludes("hooks.token");
    });

    it("supports provider builder mode under secrets.providers.<alias>", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "secrets.providers.vaultfile",
        "--provider-source",
        "file",
        "--provider-path",
        "/tmp/vault.json",
        "--provider-mode",
        "json",
        "--provider-allow-insecure-path",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.secrets?.providers?.vaultfile).toEqual({
        source: "file",
        path: "/tmp/vault.json",
        mode: "json",
        allowInsecurePath: true,
      });
    });

    it("rejects exponent-style provider builder integer options", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "secrets.providers.runner",
          "--provider-source",
          "exec",
          "--provider-command",
          "op",
          "--provider-timeout-ms",
          "1e3",
        ]),
      ).rejects.toThrow("--provider-timeout-ms must be a positive integer.");

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("runs resolvability checks in builder dry-run mode without writing", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "default",
        "--ref-source",
        "env",
        "--ref-id",
        "DISCORD_BOT_TOKEN",
        "--dry-run",
      ]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
      const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
      expect(secretRef).toEqual({
        source: "env",
        provider: "default",
        id: "DISCORD_BOT_TOKEN",
      });
      expect(requireRecord(resolveOptions, "resolve options").env).toBeTypeOf("object");
    });

    it("requires schema validation in JSON dry-run mode", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "gateway.port",
          '"not-a-number"',
          "--strict-json",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("Dry run failed: config schema validation failed.");
    });

    it("dry-runs config patch channel fields against plugin-owned schemas", async () => {
      setExternalFeishuSchema();
      const resolved: OpenClawConfig = {
        channels: {
          feishu: {
            appId: "app-id",
            appSecret: "secret",
          },
        },
      };
      setSnapshot(resolved, resolved);
      const pathname = writeTempJson5File("openclaw-config-plugin-channel-schema", {
        channels: {
          feishu: {
            appId: "app-id",
            appSecret: "secret",
            replyMode: "thread",
            footer: "OpenClaw",
          },
        },
      });

      await runConfigCommand(["config", "patch", "--file", pathname, "--dry-run"]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockError).not.toHaveBeenCalledWith(expect.stringContaining("replyMode"));
      expect(mockError).not.toHaveBeenCalledWith(expect.stringContaining("footer"));
    });

    it("fails dry-run when unsupported mutable paths receive SecretRef objects in value/json mode", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "hooks.token",
          '{"source":"env","provider":"default","id":"HOOK_TOKEN"}',
          "--strict-json",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("Dry run failed: config schema validation failed.");
      expectErrorIncludes("hooks.token");
    });

    it("aggregates policy failures across batch entries", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "--batch-json",
          '[{"path":"hooks.token","ref":{"source":"env","provider":"default","id":"HOOK_TOKEN"}},{"path":"commands.ownerDisplaySecret","ref":{"source":"env","provider":"default","id":"OWNER_DISPLAY_SECRET"}}]',
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("hooks.token");
      expectErrorIncludes("commands.ownerDisplaySecret");
    });

    it("does not duplicate policy errors in --dry-run --json mode for parent-object writes", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "hooks",
          '{"token":{"source":"env","provider":"default","id":"HOOK_TOKEN"}}',
          "--strict-json",
          "--dry-run",
          "--json",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      const payload = parseLastLogPayload() as {
        ok: boolean;
        checks: { schema: boolean; resolvability: boolean; resolvabilityComplete: boolean };
        errors?: Array<{ kind: string; message: string; ref?: string }>;
      };
      expect(payload.ok).toBe(false);
      expect(payload.checks.schema).toBe(true);
      const hooksTokenErrors =
        payload.errors?.filter(
          (entry) => entry.kind === "schema" && entry.message.includes("hooks.token"),
        ) ?? [];
      expect(hooksTokenErrors).toHaveLength(1);
    });

    it("logs a dry-run note when value mode performs no validation checks", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "gateway.port", "19001", "--dry-run"]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
      expectLogIncludes("Dry run note: value mode does not run schema/resolvability checks.");
      expectLogIncludes("Dry run successful: 1 update(s) validated");
    });

    it("supports batch mode for refs/providers in dry-run", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "--batch-json",
        '[{"path":"channels.discord.token","ref":{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}},{"path":"secrets.providers.default","provider":{"source":"env"}}]',
        "--dry-run",
      ]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
    });

    it("skips exec SecretRef resolvability checks in dry-run by default", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            runner: {
              source: "exec",
              command: "/usr/bin/env",
              allowInsecurePath: true,
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "runner",
        "--ref-source",
        "exec",
        "--ref-id",
        "openai",
        "--dry-run",
      ]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
      expectLogIncludes(
        "Dry run note: skipped 1 exec SecretRef resolvability check(s). Re-run with --allow-exec",
      );
    });

    it("allows exec SecretRef resolvability checks in dry-run when --allow-exec is set", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            runner: {
              source: "exec",
              command: "/usr/bin/env",
              allowInsecurePath: true,
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "runner",
        "--ref-source",
        "exec",
        "--ref-id",
        "openai",
        "--dry-run",
        "--allow-exec",
      ]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
      const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
      const secretRefRecord = requireRecord(secretRef, "exec SecretRef");
      expect(secretRefRecord.source).toBe("exec");
      expect(secretRefRecord.provider).toBe("runner");
      expect(secretRefRecord.id).toBe("openai");
      expect(resolveOptions).toBeTypeOf("object");
      expectLogExcludes("Dry run note: skipped 1 exec SecretRef resolvability check(s).");
    });

    it("rejects --allow-exec without --dry-run", async () => {
      const nonexistentBatchPath = path.join(
        os.tmpdir(),
        `openclaw-config-batch-nonexistent-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
      );
      await expect(
        runConfigCommand(["config", "set", "--batch-file", nonexistentBatchPath, "--allow-exec"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
      expectErrorIncludes("config set mode error: --allow-exec requires --dry-run.");
    });

    it("fails dry-run when skipped exec refs use an unconfigured provider", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {},
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "channels.discord.token",
          "--ref-provider",
          "runner",
          "--ref-source",
          "exec",
          "--ref-id",
          "openai",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
      expectErrorIncludes('Secret provider "runner" is not configured');
    });

    it("fails dry-run when skipped exec refs use a provider with mismatched source", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            runner: {
              source: "env",
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "channels.discord.token",
          "--ref-provider",
          "runner",
          "--ref-source",
          "exec",
          "--ref-id",
          "openai",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
      expectErrorIncludes('Secret provider "runner" has source "env" but ref requests "exec".');
    });

    it("writes sibling SecretRef paths when target uses sibling-ref shape", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        channels: {
          googlechat: {
            enabled: true,
          } as never,
        } as never,
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.googlechat.serviceAccount",
        "--ref-provider",
        "vaultfile",
        "--ref-source",
        "file",
        "--ref-id",
        "/providers/googlechat/serviceAccount",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.channels?.googlechat?.serviceAccountRef).toEqual({
        source: "file",
        provider: "vaultfile",
        id: "/providers/googlechat/serviceAccount",
      });
      expect(written.channels?.googlechat?.serviceAccount).toBeUndefined();
    });

    it("rejects mixing ref-builder and provider-builder flags", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "channels.discord.token",
          "--ref-provider",
          "default",
          "--ref-source",
          "env",
          "--ref-id",
          "DISCORD_BOT_TOKEN",
          "--provider-source",
          "env",
        ]),
      ).rejects.toThrow("__exit__:1");

      expectErrorIncludes("config set mode error: choose exactly one mode");
    });

    it("rejects mixing batch mode with builder flags", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "--batch-json",
          "[]",
          "--ref-provider",
          "default",
          "--ref-source",
          "env",
          "--ref-id",
          "DISCORD_BOT_TOKEN",
        ]),
      ).rejects.toThrow("__exit__:1");

      expectErrorIncludes(
        "config set mode error: batch mode (--batch-json/--batch-file) cannot be combined",
      );
    });

    it("supports batch-file mode", async () => {
      const resolved: OpenClawConfig = { gateway: { port: 18789 } };
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-batch-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
      );
      fs.writeFileSync(pathname, '[{"path":"gateway.auth.mode","value":"token"}]', "utf8");
      try {
        await runConfigCommand(["config", "set", "--batch-file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.gateway?.auth).toEqual({ mode: "token" });
    });

    it("batch-file nested leaf updates preserve agents defaults and list siblings", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.4": { alias: "GPT" },
            },
            model: { primary: "openai/gpt-5.4" },
          },
          list: [{ id: "main" }, { id: "ops" }],
        },
        plugins: {
          entries: {
            "github-copilot": { enabled: true },
          },
        },
      };
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-memory-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify([
          { path: "agents.defaults.memorySearch.enabled", value: true },
          { path: "agents.defaults.memorySearch.provider", value: "gemini" },
          { path: "agents.defaults.memorySearch.sources", value: ["memory"] },
        ]),
        "utf8",
      );
      try {
        await runConfigCommand(["config", "set", "--batch-file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.defaults?.models).toEqual(resolved.agents?.defaults?.models);
      expect(written.agents?.defaults?.model).toEqual(resolved.agents?.defaults?.model);
      expect(written.agents?.defaults?.memorySearch).toEqual({
        enabled: true,
        provider: "gemini",
        sources: ["memory"],
      });
      expect(written.agents?.list).toEqual(resolved.agents?.list);
      expect(written.plugins).toEqual(resolved.plugins);
    });

    it("rejects malformed batch-file payloads", async () => {
      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-batch-invalid-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
      );
      fs.writeFileSync(pathname, '{"path":"gateway.auth.mode","value":"token"}', "utf8");
      try {
        await expect(runConfigCommand(["config", "set", "--batch-file", pathname])).rejects.toThrow(
          "__exit__:1",
        );
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expectErrorIncludes("--batch-file must be a JSON array.");
    });

    it("patches config from one object in one write", async () => {
      const resolved = {
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.4": { alias: "GPT 5.4" },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          channels: {
            slack: {
              enabled: true,
              mode: "socket",
              botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
              appToken: { source: "env", provider: "default", id: "SLACK_APP_TOKEN" },
              groupPolicy: "open",
              requireMention: false,
            },
            discord: {
              enabled: true,
              token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
              groupPolicy: "allowlist",
            },
          },
          agents: {
            defaults: {
              model: { primary: "openai/gpt-5.5" },
              models: {
                "openai/gpt-5.5": { params: { fastMode: true } },
              },
            },
          },
        }),
        "utf8",
      );
      try {
        await runConfigCommand(["config", "patch", "--file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as Record<string, unknown>;
      expect(
        ((written.agents as Record<string, unknown>).defaults as Record<string, unknown>).models,
      ).toEqual({
        "openai/gpt-5.4": { alias: "GPT 5.4" },
        "openai/gpt-5.5": { params: { fastMode: true } },
      });
      expect(
        (
          ((written.agents as Record<string, unknown>).defaults as Record<string, unknown>)
            .model as Record<string, unknown>
        ).primary,
      ).toBe("openai/gpt-5.5");
      expect(
        ((written.channels as Record<string, unknown>).slack as Record<string, unknown>).botToken,
      ).toEqual({ source: "env", provider: "default", id: "SLACK_BOT_TOKEN" });
      expect(
        ((written.channels as Record<string, unknown>).discord as Record<string, unknown>).token,
      ).toEqual({ source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" });
    });

    it("preserves empty object values in config patch", async () => {
      const resolved = {
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.4": { alias: "GPT 5.4" },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = writeTempJson5File("openclaw-config-patch-empty-object", {
        agents: {
          defaults: {
            models: {
              "openai/gpt-5.5": {},
            },
          },
        },
      });
      try {
        await runConfigCommand(["config", "patch", "--file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      const written = firstWrittenConfig() as Record<string, unknown>;
      expect(
        ((written.agents as Record<string, unknown>).defaults as Record<string, unknown>).models,
      ).toEqual({
        "openai/gpt-5.4": { alias: "GPT 5.4" },
        "openai/gpt-5.5": {},
      });
    });

    it("treats empty object config patches as recursive merges", async () => {
      const resolved = {
        channels: {
          slack: {
            enabled: true,
            mode: "socket",
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = writeTempJson5File("openclaw-config-patch-empty-merge", {
        channels: {
          slack: {},
        },
      });
      try {
        await runConfigCommand(["config", "patch", "--file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      const written = firstWrittenConfig() as Record<string, unknown>;
      expect((written.channels as Record<string, unknown>).slack).toEqual({
        enabled: true,
        mode: "socket",
      });
    });

    it("keeps numeric config patch object keys as object keys", async () => {
      const resolved = {
        channels: {
          discord: {
            enabled: true,
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = writeTempJson5File("openclaw-config-patch-numeric-object-key", {
        channels: {
          discord: {
            guilds: {
              "123456789012345678": {
                token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
              },
            },
          },
        },
      });
      try {
        await runConfigCommand(["config", "patch", "--file", pathname]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      const written = firstWrittenConfig() as {
        channels?: { discord?: { guilds?: unknown } };
      };
      expect(written.channels?.discord?.guilds).toEqual({
        "123456789012345678": {
          token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
        },
      });
    });

    it("dry-runs config patch and resolves changed SecretRefs", async () => {
      const resolved = {
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-dry-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          channels: {
            discord: {
              token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
            },
          },
        }),
        "utf8",
      );
      try {
        await runConfigCommand(["config", "patch", "--file", pathname, "--dry-run"]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
      const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
      expect(secretRef).toEqual({ source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" });
      expect(resolveOptions).toBeTypeOf("object");
    });

    it("dry-runs pluginIntegration provider patches against manifest integration metadata", async () => {
      const pluginId = "secret-provider-proof";
      const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-plugin-provider-"));
      try {
        fs.writeFileSync(path.join(rootDir, "index.js"), "export default {};\n", "utf8");
        fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n", "utf8");
        const resolved = {
          secrets: {
            providers: {},
          },
        } as unknown as OpenClawConfig;
        mockLoadPluginMetadataSnapshot.mockReturnValue(
          createPluginMetadataSnapshot({
            diagnostics: [],
            plugins: [
              createPluginManifestRecord({
                id: pluginId,
                enabledByDefault: true,
                origin: "bundled",
                rootDir,
                source: path.join(rootDir, "index.js"),
                manifestPath: path.join(rootDir, "openclaw.plugin.json"),
                secretProviderIntegrations: {
                  vault: {
                    source: "exec",
                    command: "${node}",
                    args: ["./resolve.mjs"],
                  },
                },
              }),
            ],
          }),
        );

        setSnapshot(resolved, resolved);
        const validPatch = writeTempJson5File("openclaw-config-plugin-provider-valid", {
          secrets: {
            providers: {
              team: {
                source: "exec",
                pluginIntegration: { pluginId, integrationId: "vault" },
              },
            },
          },
        });
        try {
          await runConfigCommand([
            "config",
            "patch",
            "--file",
            validPatch,
            "--dry-run",
            "--allow-exec",
            "--json",
          ]);
        } finally {
          fs.rmSync(validPatch, { force: true });
        }
        expect(mockWriteConfigFile).not.toHaveBeenCalled();

        setSnapshot(resolved, resolved);
        const invalidPatch = writeTempJson5File("openclaw-config-plugin-provider-invalid", {
          secrets: {
            providers: {
              team: {
                source: "exec",
                pluginIntegration: { pluginId, integrationId: "missing" },
              },
            },
          },
        });
        try {
          await expect(
            runConfigCommand([
              "config",
              "patch",
              "--file",
              invalidPatch,
              "--dry-run",
              "--allow-exec",
              "--json",
            ]),
          ).rejects.toThrow("__exit__:1");
        } finally {
          fs.rmSync(invalidPatch, { force: true });
        }
        const invalidPayload = lastMockArg(defaultRuntime.writeJson) as {
          errors?: Array<{ message?: string }>;
        };
        const errorMessages = invalidPayload.errors?.map((error) => error.message ?? "") ?? [];
        expect(errorMessages.some((message) => message.includes("secrets.providers.team"))).toBe(
          true,
        );
        expect(
          errorMessages.some((message) =>
            message.includes(`does not declare secret provider integration "missing"`),
          ),
        ).toBe(true);
      } finally {
        fs.rmSync(rootDir, { recursive: true, force: true });
      }
    });

    it("does not revalidate untouched pluginIntegration providers when disabling plugins", async () => {
      const pluginId = "secret-provider-proof";
      const resolved = {
        plugins: {
          enabled: true,
          entries: {
            [pluginId]: { enabled: true },
          },
        },
        secrets: {
          providers: {
            team: {
              source: "exec",
              pluginIntegration: { pluginId, integrationId: "vault" },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const patch = writeTempJson5File("openclaw-config-plugin-disable", {
        plugins: {
          entries: {
            [pluginId]: { enabled: false },
          },
        },
      });
      try {
        await runConfigCommand(["config", "patch", "--file", patch]);
      } finally {
        fs.rmSync(patch, { force: true });
      }

      expect(firstWrittenConfig().plugins?.entries?.[pluginId]?.enabled).toBe(false);
    });

    it("validates pluginIntegration providers referenced by newly assigned SecretRefs", async () => {
      const pluginId = "secret-provider-proof";
      const resolved = {
        gateway: {
          auth: { mode: "token" },
        },
        secrets: {
          providers: {
            team: {
              source: "exec",
              pluginIntegration: { pluginId, integrationId: "vault" },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const patch = writeTempJson5File("openclaw-config-plugin-provider-ref", {
        gateway: {
          auth: {
            token: { source: "exec", provider: "team", id: "gateway/token" },
          },
        },
      });
      try {
        await expect(
          runConfigCommand(["config", "patch", "--file", patch, "--dry-run", "--json"]),
        ).rejects.toThrow("__exit__:1");
      } finally {
        fs.rmSync(patch, { force: true });
      }

      const payload = lastMockArg(defaultRuntime.writeJson) as {
        errors?: Array<{ message?: string }>;
      };
      const messages = payload.errors?.map((error) => error.message ?? "") ?? [];
      expect(messages.some((message) => message.includes("secrets.providers.team"))).toBe(true);
      expect(messages.some((message) => message.includes(`plugin "${pluginId}"`))).toBe(true);
    });

    it("schema-validates SecretRef-only config patch operations", async () => {
      const resolved = {
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-ref-schema-${Date.now()}-${Math.random()
          .toString(16)
          .slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          gateway: {
            typo: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
          },
        }),
        "utf8",
      );
      try {
        await expect(
          runConfigCommand(["config", "patch", "--file", pathname, "--dry-run"]),
        ).rejects.toThrow("__exit__:1");
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
      expectErrorIncludes("Dry run failed: config schema validation failed.");
      expectErrorIncludes("gateway");
      expectErrorIncludes('"typo"');
    });

    it("dry-runs nested SecretRefs inside config patch replacements", async () => {
      const resolved = {
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
        channels: {
          slack: {
            enabled: false,
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockRejectedValue(new Error("missing env var"));

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-nested-ref-${Date.now()}-${Math.random()
          .toString(16)
          .slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          channels: {
            slack: {
              enabled: true,
              mode: "socket",
              botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
              appToken: { source: "env", provider: "default", id: "SLACK_APP_TOKEN" },
            },
          },
        }),
        "utf8",
      );
      try {
        await expect(
          runConfigCommand([
            "config",
            "patch",
            "--file",
            pathname,
            "--replace-path",
            "channels.slack",
            "--dry-run",
          ]),
        ).rejects.toThrow("__exit__:1");
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(2);
      expectErrorIncludes("Dry run failed: 2 SecretRef assignment(s) could not be resolved.");
    });

    it("rejects config patch --json without dry-run", async () => {
      await expect(runConfigCommand(["config", "patch", "--stdin", "--json"])).rejects.toThrow(
        "__exit__:1",
      );
      expectErrorIncludes("config patch mode error: --json requires --dry-run.");
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("supports replace-path and null deletes in config patch", async () => {
      const resolved = {
        channels: {
          slack: {
            appToken: { source: "env", provider: "default", id: "SLACK_APP_TOKEN" },
          },
          discord: {
            guilds: {
              guild: {
                channels: {
                  old: { enabled: true },
                },
              },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-replace-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          channels: {
            slack: {
              appToken: null,
            },
            discord: {
              guilds: {
                guild: {
                  channels: {
                    maintainers: { enabled: true, requireMention: true },
                  },
                },
              },
            },
          },
        }),
        "utf8",
      );
      try {
        await runConfigCommand([
          "config",
          "patch",
          "--file",
          pathname,
          "--replace-path",
          "channels.discord.guilds.guild.channels",
        ]);
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      const written = firstWrittenConfig() as Record<string, unknown>;
      const channels = (written.channels as Record<string, unknown>).discord as Record<
        string,
        unknown
      >;
      expect(
        ((channels.guilds as Record<string, unknown>).guild as Record<string, unknown>)
          .channels as Record<string, unknown>,
      ).toEqual({ maintainers: { enabled: true, requireMention: true } });
      expect((written.channels as Record<string, unknown>).slack).not.toHaveProperty("appToken");
      expect(requireWriteOptions().unsetPaths).toEqual([["channels", "slack", "appToken"]]);
    });

    it("rejects unused config patch replace paths", async () => {
      const pathname = path.join(
        os.tmpdir(),
        `openclaw-config-patch-unused-replace-${Date.now()}-${Math.random()
          .toString(16)
          .slice(2)}.json5`,
      );
      fs.writeFileSync(
        pathname,
        JSON.stringify({
          channels: {
            discord: {
              enabled: true,
            },
          },
        }),
        "utf8",
      );
      try {
        await expect(
          runConfigCommand([
            "config",
            "patch",
            "--file",
            pathname,
            "--replace-path",
            "channels.discord.guilds",
          ]),
        ).rejects.toThrow("__exit__:1");
      } finally {
        fs.rmSync(pathname, { force: true });
      }

      expectErrorIncludes(
        "config patch mode error: --replace-path channels.discord.guilds did not match any value in the input patch.",
      );
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects malformed batch entries with mixed operation keys", async () => {
      await expect(
        runConfigCommand([
          "config",
          "set",
          "--batch-json",
          '[{"path":"channels.discord.token","value":"x","ref":{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}}]',
        ]),
      ).rejects.toThrow("__exit__:1");

      expectErrorIncludes("must include exactly one of: value, ref, provider");
    });

    it("fails dry-run when a builder-assigned SecretRef is unresolved", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockRejectedValueOnce(new Error("missing env var"));

      await expect(
        runConfigCommand([
          "config",
          "set",
          "channels.discord.token",
          "--ref-provider",
          "default",
          "--ref-source",
          "env",
          "--ref-id",
          "DISCORD_BOT_TOKEN",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
    });

    it("emits structured JSON for --dry-run --json success", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "default",
        "--ref-source",
        "env",
        "--ref-id",
        "DISCORD_BOT_TOKEN",
        "--dry-run",
        "--json",
      ]);

      const payload = parseLastLogPayload() as {
        ok: boolean;
        checks: { schema: boolean; resolvability: boolean; resolvabilityComplete: boolean };
        refsChecked: number;
        skippedExecRefs: number;
        operations: number;
      };
      expect(payload.ok).toBe(true);
      expect(payload.operations).toBe(1);
      expect(payload.refsChecked).toBe(1);
      expect(payload.skippedExecRefs).toBe(0);
      expect(payload.checks).toEqual({
        schema: false,
        resolvability: true,
        resolvabilityComplete: true,
      });
    });

    it("emits skipped exec metadata for --dry-run --json success", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            runner: {
              source: "exec",
              command: "/usr/bin/env",
              allowInsecurePath: true,
            },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.token",
        "--ref-provider",
        "runner",
        "--ref-source",
        "exec",
        "--ref-id",
        "openai",
        "--dry-run",
        "--json",
      ]);

      const payload = parseLastLogPayload() as {
        ok: boolean;
        checks: { resolvability: boolean; resolvabilityComplete: boolean };
        refsChecked: number;
        skippedExecRefs: number;
      };
      expect(payload.ok).toBe(true);
      expect(payload.checks.resolvability).toBe(true);
      expect(payload.checks.resolvabilityComplete).toBe(false);
      expect(payload.refsChecked).toBe(0);
      expect(payload.skippedExecRefs).toBe(1);
    });

    it("emits structured JSON for --dry-run --json failure", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockRejectedValueOnce(new Error("missing env var"));

      await expect(
        runConfigCommand([
          "config",
          "set",
          "channels.discord.token",
          "--ref-provider",
          "default",
          "--ref-source",
          "env",
          "--ref-id",
          "DISCORD_BOT_TOKEN",
          "--dry-run",
          "--json",
        ]),
      ).rejects.toThrow("__exit__:1");

      const payload = parseLastLogPayload() as {
        ok: boolean;
        errors?: Array<{ kind: string; message: string; ref?: string }>;
      };
      expect(payload.ok).toBe(false);
      const errorKinds = (payload.errors ?? []).map((entry) => entry.kind);
      expect(errorKinds).toContain("resolvability");
      const errorRefs = (payload.errors ?? []).map((entry) => entry.ref ?? "");
      expect(errorRefs).toContain("env:default:DISCORD_BOT_TOKEN");
    });

    it("keeps distinct resolvability failures when messages are identical but refs differ", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand([
          "config",
          "set",
          "--batch-json",
          '[{"path":"channels.discord.token","ref":{"source":"exec","provider":"default","id":"DISCORD_BOT_TOKEN"}},{"path":"channels.telegram.botToken","ref":{"source":"exec","provider":"default","id":"TELEGRAM_BOT_TOKEN"}}]',
          "--dry-run",
          "--json",
        ]),
      ).rejects.toThrow("__exit__:1");

      const payload = parseLastLogPayload() as {
        ok: boolean;
        errors?: Array<{ kind: string; message: string; ref?: string }>;
      };
      expect(payload.ok).toBe(false);
      const resolvabilityErrors =
        payload.errors?.filter((entry) => entry.kind === "resolvability") ?? [];
      expect(resolvabilityErrors).toHaveLength(2);
      expect(
        resolvabilityErrors.some((entry) => entry.ref === "exec:default:DISCORD_BOT_TOKEN"),
      ).toBe(true);
      expect(
        resolvabilityErrors.some((entry) => entry.ref === "exec:default:TELEGRAM_BOT_TOKEN"),
      ).toBe(true);
    });

    it("aggregates schema and resolvability failures in --dry-run --json mode", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      };
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockRejectedValue(new Error("missing env var"));

      await expect(
        runConfigCommand([
          "config",
          "set",
          "--batch-json",
          '[{"path":"gateway.port","value":"not-a-number"},{"path":"channels.discord.token","ref":{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}}]',
          "--dry-run",
          "--json",
        ]),
      ).rejects.toThrow("__exit__:1");

      const payload = parseLastLogPayload() as {
        ok: boolean;
        errors?: Array<{ kind: string; message: string; ref?: string }>;
      };
      expect(payload.ok).toBe(false);
      const errorKinds = (payload.errors ?? []).map((entry) => entry.kind);
      expect(errorKinds).toContain("schema");
      expect(errorKinds).toContain("resolvability");
      const errorRefs = (payload.errors ?? []).map((entry) => entry.ref ?? "");
      expect(errorRefs).toContain("env:default:DISCORD_BOT_TOKEN");
    });

    it("fails dry-run when provider updates make existing refs unresolvable", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" },
          },
        },
        tools: {
          web: {
            search: {
              enabled: true,
              apiKey: {
                source: "file",
                provider: "vaultfile",
                id: "/providers/search/apiKey",
              },
            },
          },
        } as never,
      };
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockImplementationOnce(async () => {
        throw new Error("provider mismatch");
      });

      await expect(
        runConfigCommand([
          "config",
          "set",
          "secrets.providers.vaultfile",
          "--provider-source",
          "env",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
      expectErrorIncludes("provider mismatch");
    });

    it("fails dry-run for nested provider edits that make existing refs unresolvable", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" },
          },
        },
        tools: {
          web: {
            search: {
              enabled: true,
              apiKey: {
                source: "file",
                provider: "vaultfile",
                id: "/providers/search/apiKey",
              },
            },
          },
        } as never,
      };
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockImplementationOnce(async () => {
        throw new Error("provider mismatch");
      });

      await expect(
        runConfigCommand([
          "config",
          "set",
          "secrets.providers.vaultfile.path",
          '"/tmp/other-secrets.json"',
          "--strict-json",
          "--dry-run",
        ]),
      ).rejects.toThrow("__exit__:1");

      const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
      const secretRefRecord = requireRecord(secretRef, "existing SecretRef");
      expect(secretRefRecord.provider).toBe("vaultfile");
      expect(secretRefRecord.id).toBe("/providers/search/apiKey");
      expect(resolveOptions).toBeTypeOf("object");
      expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
      expectErrorIncludes("provider mismatch");
    });
  });

  describe("path hardening", () => {
    it("rejects blocked prototype-key segments for config get", async () => {
      await expect(runConfigCommand(["config", "get", "gateway.__proto__.token"])).rejects.toThrow(
        "Invalid path segment: __proto__",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects blocked prototype-key segments for config set", async () => {
      await expect(
        runConfigCommand(["config", "set", "tools.constructor.profile", '"sandbox"']),
      ).rejects.toThrow("Invalid path segment: constructor");

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects blocked prototype-key segments for config unset", async () => {
      await expect(
        runConfigCommand(["config", "unset", "channels.prototype.enabled"]),
      ).rejects.toThrow("Invalid path segment: prototype");

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects impractical array indexes for config set", async () => {
      const resolved = { agents: { list: [] } } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand(["config", "set", "agents.list.4294967294.id", '"main"']),
      ).rejects.toThrow('Expected numeric index for array segment "4294967294"');

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects signed array indexes for config set", async () => {
      const resolved = { agents: { list: [{ id: "main" }] } } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand(["config", "set", "agents.list.+0.id", '"other"']),
      ).rejects.toThrow('Expected numeric index for array segment "+0"');

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects double-dot empty segments for config set instead of writing a different key", async () => {
      await expect(runConfigCommand(["config", "set", "gateway..port", "23456"])).rejects.toThrow(
        "Invalid path (empty segment): gateway..port",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects leading-dot empty segments for config get", async () => {
      await expect(runConfigCommand(["config", "get", ".gateway.port"])).rejects.toThrow(
        "Invalid path (empty segment): .gateway.port",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects trailing-dot empty segments for config unset", async () => {
      await expect(runConfigCommand(["config", "unset", "gateway.port."])).rejects.toThrow(
        "Invalid path (empty segment): gateway.port.",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects whitespace-only segments for config set", async () => {
      await expect(runConfigCommand(["config", "set", "gateway. .port", "23456"])).rejects.toThrow(
        "Invalid path (empty segment): gateway. .port",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects an empty segment before a bracket for config set", async () => {
      await expect(runConfigCommand(["config", "set", "gateway.[port]", "23456"])).rejects.toThrow(
        "Invalid path (empty segment): gateway.[port]",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects a whitespace-only segment after a bracket before a dot", async () => {
      await expect(runConfigCommand(["config", "get", "agents.list[0] .id"])).rejects.toThrow(
        "Invalid path (empty segment): agents.list[0] .id",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("rejects a whitespace-only segment after a bracket before another bracket", async () => {
      await expect(runConfigCommand(["config", "get", "agents.list[0] [1]"])).rejects.toThrow(
        "Invalid path (empty segment): agents.list[0] [1]",
      );

      expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("preserves valid bracket path forms", async () => {
      const resolved: OpenClawConfig = {
        agents: { list: [{ id: "main" }, { id: "other" }] },
      };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "set", "agents.list[1].id", "renamed"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.list?.[1]?.id).toBe("renamed");
      expect(written.agents?.list?.[0]?.id).toBe("main");
    });

    it("preserves escaped dots inside path segments", async () => {
      const resolved = {
        channels: {
          discord: {
            guilds: {
              "prod.guild": { channels: ["alerts"] },
              staging: { channels: ["chat"] },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await runConfigCommand([
        "config",
        "set",
        "channels.discord.guilds.prod\\.guild.channels",
        '["alerts","ops"]',
        "--strict-json",
      ]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as {
        channels?: { discord?: { guilds?: Record<string, { channels?: string[] }> } };
      };
      expect(written.channels?.discord?.guilds?.["prod.guild"]?.channels).toEqual([
        "alerts",
        "ops",
      ]);
      expect(written.channels?.discord?.guilds?.staging?.channels).toEqual(["chat"]);
    });
  });

  describe("config unset - issue #6070", () => {
    it("preserves existing config keys when unsetting a value", async () => {
      const resolved: OpenClawConfig = {
        agents: { list: [{ id: "main" }] },
        gateway: { port: 18789 },
        tools: {
          profile: "coding",
          alsoAllow: ["agents_list"],
        },
        logging: { level: "debug" },
      };
      const runtimeMerged: OpenClawConfig = {
        ...withRuntimeDefaults(resolved),
      };
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand(["config", "unset", "tools.alsoAllow"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.tools).not.toHaveProperty("alsoAllow");
      expect(written.agents).not.toHaveProperty("defaults");
      expect(written.agents?.list).toEqual(resolved.agents?.list);
      expect(written.gateway).toEqual(resolved.gateway);
      expect(written.tools?.profile).toBe("coding");
      expect(written.logging).toEqual(resolved.logging);
      expect(firstWriteConfigOptions()).toEqual({
        unsetPaths: [["tools", "alsoAllow"]],
      });
    });

    it("removes only the specified array element", async () => {
      const resolved: OpenClawConfig = {
        agents: {
          list: [{ id: "agent-a" }, { id: "agent-b" }, { id: "agent-c" }],
        },
      };
      const runtimeMerged: OpenClawConfig = {
        ...withRuntimeDefaults(resolved),
      };
      setSnapshot(resolved, runtimeMerged);

      await runConfigCommand(["config", "unset", "agents.list[1]"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig();
      expect(written.agents?.list).toEqual([{ id: "agent-a" }, { id: "agent-c" }]);
      expect(firstWriteConfigOptions()).toBeUndefined();
    });

    it("preserves write-level unset handling for numeric object keys", async () => {
      const resolved: OpenClawConfig = {
        channels: {
          discord: {
            guilds: {
              "123": { channels: ["general"] },
              "456": { channels: ["alerts"] },
            },
          },
        },
      } as unknown as OpenClawConfig;
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "unset", "channels.discord.guilds.123"]);

      expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
      const written = firstWrittenConfig() as {
        channels?: { discord?: { guilds?: Record<string, unknown> } };
      };
      expect(written.channels?.discord?.guilds).toEqual({
        "456": { channels: ["alerts"] },
      });
      expect(firstWriteConfigOptions()).toEqual({
        unsetPaths: [["channels", "discord", "guilds", "123"]],
      });
    });

    it("dry-runs an unset without writing the config file", async () => {
      const resolved: OpenClawConfig = {
        agents: { list: [{ id: "main" }] },
        gateway: { port: 18789 },
        tools: {
          profile: "coding",
          alsoAllow: ["agents_list"],
        },
      };
      setSnapshot(resolved, resolved);
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "unset", "tools.alsoAllow", "--dry-run"]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectLogIncludes("Dry run successful: 1 update(s) validated against /tmp/openclaw.json.");
      expect(mockReadConfigFileSnapshot).toHaveBeenCalledTimes(2);
    });

    it("prints JSON for config unset dry-run", async () => {
      const resolved: OpenClawConfig = {
        agents: { list: [{ id: "main" }] },
        gateway: { port: 18789 },
        tools: {
          profile: "coding",
          alsoAllow: ["agents_list"],
        },
      };
      setSnapshot(resolved, resolved);
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "unset", "tools.alsoAllow", "--dry-run", "--json"]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(parseLastLogPayload()).toMatchObject({
        ok: true,
        operations: 1,
        inputModes: ["unset"],
        checks: {
          schema: true,
          resolvability: true,
          resolvabilityComplete: true,
        },
      });
    });

    it("prints structured JSON when unset dry-run misses a path", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        tools: {
          profile: "coding",
        },
      };
      setSnapshot(resolved, resolved);

      await expect(
        runConfigCommand(["config", "unset", "tools.alsoAllow", "--dry-run", "--json"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expect(mockError).not.toHaveBeenCalled();
      const payload = parseLastLogPayload() as {
        ok: boolean;
        inputModes: string[];
        checks: { schema: boolean; resolvability: boolean; resolvabilityComplete: boolean };
        errors?: Array<{ kind: string; message: string }>;
      };
      expect(payload.ok).toBe(false);
      expect(payload.inputModes).toEqual(["unset"]);
      expect(payload.checks).toEqual({
        schema: false,
        resolvability: false,
        resolvabilityComplete: false,
      });
      expect(payload.errors).toEqual([
        {
          kind: "missing-path",
          message: "Config path not found: tools.alsoAllow. Nothing was changed.",
        },
      ]);
    });

    it("validates existing refs when unset dry-run removes all secret providers", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          providers: {
            vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" },
          },
        },
        tools: {
          web: {
            search: {
              enabled: true,
              apiKey: {
                source: "file",
                provider: "vaultfile",
                id: "/providers/search/apiKey",
              },
            },
          },
        } as never,
      };
      setSnapshot(resolved, resolved);
      setSnapshot(resolved, resolved);
      mockResolveSecretRefValue.mockRejectedValueOnce(new Error("provider removed"));

      await expect(
        runConfigCommand(["config", "unset", "secrets.providers", "--dry-run"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      const [secretRef] = requireResolveSecretRefCall(0);
      const secretRefRecord = requireRecord(secretRef, "existing SecretRef");
      expect(secretRefRecord.provider).toBe("vaultfile");
      expect(secretRefRecord.id).toBe("/providers/search/apiKey");
      expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
      expectErrorIncludes("provider removed");
    });

    it("validates existing refs when unset dry-run removes secret defaults", async () => {
      const resolved: OpenClawConfig = {
        gateway: { port: 18789 },
        secrets: {
          defaults: {
            env: "vaultenv",
          },
          providers: {
            default: { source: "env" },
            vaultenv: { source: "env" },
          },
        },
        tools: {
          web: {
            search: {
              enabled: true,
              apiKey: "${WEB_SEARCH_API_KEY}",
            },
          },
        } as never,
      } as OpenClawConfig;
      setSnapshot(resolved, resolved);
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "unset", "secrets.defaults", "--dry-run"]);

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      const [secretRef] = requireResolveSecretRefCall(0);
      const secretRefRecord = requireRecord(secretRef, "defaulted SecretRef");
      expect(secretRefRecord).toMatchObject({
        source: "env",
        provider: "default",
        id: "WEB_SEARCH_API_KEY",
      });
      expectLogIncludes("Dry run successful: 1 update(s) validated against /tmp/openclaw.json.");
    });

    it("rejects config unset --json without --dry-run", async () => {
      await expect(
        runConfigCommand(["config", "unset", "tools.alsoAllow", "--json"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("--json can only be used with --dry-run.");
    });

    it("rejects config unset --allow-exec without --dry-run", async () => {
      await expect(
        runConfigCommand(["config", "unset", "tools.alsoAllow", "--allow-exec"]),
      ).rejects.toThrow("__exit__:1");

      expect(mockWriteConfigFile).not.toHaveBeenCalled();
      expectErrorIncludes("--allow-exec can only be used with --dry-run.");
    });
  });

  describe("config file", () => {
    it("prints the active config file path", async () => {
      const resolved: OpenClawConfig = { gateway: { port: 18789 } };
      setSnapshot(resolved, resolved);

      await runConfigCommand(["config", "file"]);

      expect(mockLog).toHaveBeenCalledWith("/tmp/openclaw.json");
      expect(mockWriteConfigFile).not.toHaveBeenCalled();
    });

    it("handles config file path with home directory", async () => {
      const resolved: OpenClawConfig = { gateway: { port: 18789 } };
      const snapshot = buildSnapshot({ resolved, config: resolved });
      snapshot.path = "/home/user/.openclaw/openclaw.json";
      mockReadConfigFileSnapshot.mockResolvedValueOnce(snapshot);

      await runConfigCommand(["config", "file"]);

      expect(mockLog).toHaveBeenCalledWith("/home/user/.openclaw/openclaw.json");
    });
  });
});
