fix(cli): avoid catalog validation in agents add (#88314)

Fixes #76284.

Thanks @zhangguiping-xydt.

Co-authored-by: 张贵萍0668001030 <zhang.guiping@xydigit.com>
This commit is contained in:
zhang-guiping
2026-06-01 02:22:16 +08:00
committed by GitHub
parent a88e4fb7e0
commit 2fbddce881
4 changed files with 66 additions and 2 deletions

View File

@@ -257,7 +257,8 @@ jobs:
return -1;
}
for (let start = input.indexOf("["); start !== -1; start = input.indexOf("[", start + 1)) {
for (const match of input.matchAll(/\[/g)) {
const start = match.index;
const end = arrayEndFrom(start);
if (end === -1) {
continue;

View File

@@ -12,6 +12,12 @@ const writeConfigFileMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined
const replaceConfigFileMock = vi.hoisted(() =>
vi.fn(async (params: { nextConfig: unknown }) => await writeConfigFileMock(params.nextConfig)),
);
const commitConfigWithPendingPluginInstallsMock = vi.hoisted(() =>
vi.fn(async (params: { nextConfig: Record<string, unknown> }) => {
await writeConfigFileMock(params.nextConfig);
return { config: params.nextConfig };
}),
);
const transformConfigWithPendingPluginInstallsMock = vi.hoisted(() =>
vi.fn(
async (params: {
@@ -56,6 +62,16 @@ const transformConfigWithPendingPluginInstallsMock = vi.hoisted(() =>
const wizardMocks = vi.hoisted(() => ({
createClackPrompter: vi.fn(),
}));
const authChoiceMocks = vi.hoisted(() => ({
applyAuthChoice: vi.fn(),
warnIfModelConfigLooksOff: vi.fn(async () => {}),
}));
const onboardChannelsMocks = vi.hoisted(() => ({
setupChannels: vi.fn(async (config: Record<string, unknown>) => config),
}));
const onboardHelpersMocks = vi.hoisted(() => ({
ensureWorkspaceAndSessions: vi.fn(async () => {}),
}));
vi.mock("../config/config.js", async () => ({
...(await vi.importActual<typeof import("../config/config.js")>("../config/config.js")),
@@ -68,6 +84,7 @@ vi.mock("../cli/plugins-install-record-commit.js", async () => ({
...(await vi.importActual<typeof import("../cli/plugins-install-record-commit.js")>(
"../cli/plugins-install-record-commit.js",
)),
commitConfigWithPendingPluginInstalls: commitConfigWithPendingPluginInstallsMock,
transformConfigWithPendingPluginInstalls: transformConfigWithPendingPluginInstallsMock,
}));
@@ -75,6 +92,19 @@ vi.mock("../wizard/clack-prompter.js", () => ({
createClackPrompter: wizardMocks.createClackPrompter,
}));
vi.mock("./auth-choice.js", () => ({
applyAuthChoice: authChoiceMocks.applyAuthChoice,
warnIfModelConfigLooksOff: authChoiceMocks.warnIfModelConfigLooksOff,
}));
vi.mock("./onboard-channels.js", () => ({
setupChannels: onboardChannelsMocks.setupChannels,
}));
vi.mock("./onboard-helpers.js", () => ({
ensureWorkspaceAndSessions: onboardHelpersMocks.ensureWorkspaceAndSessions,
}));
import { WizardCancelledError } from "../wizard/prompts.js";
import { agentsAddCommand, testing } from "./agents.commands.add.js";
@@ -85,8 +115,13 @@ describe("agents add command", () => {
readConfigFileSnapshotMock.mockClear();
writeConfigFileMock.mockClear();
replaceConfigFileMock.mockClear();
commitConfigWithPendingPluginInstallsMock.mockClear();
transformConfigWithPendingPluginInstallsMock.mockClear();
wizardMocks.createClackPrompter.mockClear();
authChoiceMocks.applyAuthChoice.mockClear();
authChoiceMocks.warnIfModelConfigLooksOff.mockClear();
onboardChannelsMocks.setupChannels.mockClear();
onboardHelpersMocks.ensureWorkspaceAndSessions.mockClear();
runtime.log.mockClear();
runtime.error.mockClear();
runtime.exit.mockClear();
@@ -136,6 +171,33 @@ describe("agents add command", () => {
expect(writeConfigFileMock).not.toHaveBeenCalled();
});
it("skips catalog validation when checking the interactive wizard model config", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
...baseConfigSnapshot,
config: { agents: { list: [] } },
sourceConfig: { agents: { list: [] } },
});
wizardMocks.createClackPrompter.mockReturnValue({
intro: vi.fn(),
text: vi.fn().mockResolvedValueOnce("Jon").mockResolvedValueOnce("/tmp/openclaw-jon"),
confirm: vi.fn().mockResolvedValue(false),
note: vi.fn(),
outro: vi.fn(),
});
await agentsAddCommand({}, runtime);
expect(authChoiceMocks.warnIfModelConfigLooksOff).toHaveBeenCalledOnce();
expect(authChoiceMocks.warnIfModelConfigLooksOff).toHaveBeenCalledWith(
expect.objectContaining({ agents: expect.any(Object) }),
expect.any(Object),
expect.objectContaining({
agentId: "jon",
validateCatalog: false,
}),
);
});
it("copies only portable auth profiles when seeding a new agent store", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agents-add-auth-copy-"));
try {

View File

@@ -415,6 +415,7 @@ export async function agentsAddCommand(
await warnIfModelConfigLooksOff(nextConfig, prompter, {
agentId,
agentDir,
validateCatalog: false,
});
let selection: ChannelChoice[] = [];

View File

@@ -57,7 +57,7 @@
document.documentElement.setAttribute("data-theme", resolved);
document.documentElement.setAttribute(
"data-theme-mode",
resolved.indexOf("light") !== -1 ? "light" : "dark",
resolved.includes("light") ? "light" : "dark",
);
} catch (e) {}
})();