


























@@ -26,6 +26,8 @@ const mocks = vi.hoisted(() => {
2626waitForGatewayReachable: vi.fn(),
2727resolveControlUiLinks: vi.fn(),
2828summarizeExistingConfig: vi.fn(),
29+promptAuthConfig: vi.fn(),
30+promptGatewayConfig: vi.fn(),
2931promptRemoteGatewayConfig: vi.fn(async (cfg: OpenClawConfig) => ({
3032 ...cfg,
3133gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } },
@@ -86,11 +88,11 @@ vi.mock("./health-format.js", () => ({
8688}));
87898890vi.mock("./configure.gateway.js", () => ({
89-promptGatewayConfig: vi.fn(),
91+promptGatewayConfig: mocks.promptGatewayConfig,
9092}));
91939294vi.mock("./configure.gateway-auth.js", () => ({
93-promptAuthConfig: vi.fn(),
95+promptAuthConfig: mocks.promptAuthConfig,
9496}));
95979698vi.mock("./configure.channels.js", () => ({
@@ -276,6 +278,13 @@ describe("runConfigureWizard", () => {
276278]);
277279mocks.setupSearch.mockReset();
278280mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => cfg);
281+mocks.promptAuthConfig.mockReset();
282+mocks.promptAuthConfig.mockImplementation(async (cfg: OpenClawConfig) => cfg);
283+mocks.promptGatewayConfig.mockReset();
284+mocks.promptGatewayConfig.mockImplementation(async (cfg: OpenClawConfig) => ({
285+config: cfg,
286+port: 18789,
287+}));
279288});
280289281290it("persists gateway.mode=local when only the run mode is selected", async () => {
@@ -328,6 +337,46 @@ describe("runConfigureWizard", () => {
328337expect(runtime.exit).toHaveBeenCalledWith(1);
329338});
330339340+it("does not gate model-only configure behind Gateway run-mode selection", async () => {
341+setupBaseWizardState();
342+343+await runConfigureWizard({ command: "configure", sections: ["model"] }, createRuntime());
344+345+expect(mocks.promptAuthConfig).toHaveBeenCalledOnce();
346+expect(mocks.clackSelect).not.toHaveBeenCalledWith(
347+expect.objectContaining({ message: "Where will the Gateway run?" }),
348+);
349+expect(mocks.probeGatewayReachable).not.toHaveBeenCalledWith(
350+expect.objectContaining({ timeoutMs: 300 }),
351+);
352+expect(mocks.ensureControlUiAssetsBuilt).not.toHaveBeenCalled();
353+expect(mocks.resolveControlUiLinks).not.toHaveBeenCalled();
354+expect(requireWriteConfig().gateway).toBeUndefined();
355+});
356+357+it("runs model-only configure for existing remote Gateway configs", async () => {
358+setupBaseWizardState({
359+gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } },
360+});
361+362+await runConfigureWizard({ command: "configure", sections: ["model"] }, createRuntime());
363+364+expect(mocks.promptAuthConfig).toHaveBeenCalledOnce();
365+expect(mocks.promptRemoteGatewayConfig).not.toHaveBeenCalled();
366+expect(getGateway(requireWriteConfig()).mode).toBe("remote");
367+expect(mocks.ensureControlUiAssetsBuilt).not.toHaveBeenCalled();
368+expect(mocks.resolveControlUiLinks).not.toHaveBeenCalled();
369+expect(mocks.probeGatewayReachable).not.toHaveBeenCalled();
370+expect(mocks.note).toHaveBeenCalledWith(
371+[
372+"Remote Gateway:",
373+"wss://gateway.example.test",
374+"Docs: https://docs.openclaw.ai/gateway/remote",
375+].join("\n"),
376+"Gateway",
377+);
378+});
379+331380it("persists provider-owned web search config changes returned by setupSearch", async () => {
332381setupBaseWizardState();
333382mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) =>
@@ -337,7 +386,7 @@ describe("runConfigureWizard", () => {
337386})(cfg),
338387);
339388queueWizardPrompts({
340-select: ["local"],
389+select: [],
341390confirm: [true, false],
342391});
343392@@ -347,7 +396,7 @@ describe("runConfigureWizard", () => {
347396mockCallArg(mocks.setupSearch, "setupSearch"),
348397"setupSearch config",
349398);
350-expect(getGateway(setupConfig).mode).toBe("local");
399+expect(setupConfig.gateway).toBeUndefined();
351400const written = requireWriteConfig();
352401const search = getWebSearch(written);
353402expect(search.provider).toBe("firecrawl");
@@ -365,7 +414,7 @@ describe("runConfigureWizard", () => {
365414setupBaseWizardState();
366415mocks.resolveSearchProviderOptions.mockReturnValue([]);
367416queueWizardPrompts({
368-select: ["local"],
417+select: [],
369418confirm: [true, false],
370419});
371420@@ -385,7 +434,7 @@ describe("runConfigureWizard", () => {
385434it("does not load managed search provider options when web search is disabled", async () => {
386435setupBaseWizardState();
387436queueWizardPrompts({
388-select: ["local"],
437+select: [],
389438confirm: [false, true],
390439});
391440@@ -404,15 +453,15 @@ describe("runConfigureWizard", () => {
404453it("defers channel status checks until a channel is selected", async () => {
405454setupBaseWizardState();
406455queueWizardPrompts({
407-select: ["local", "configure"],
456+select: ["configure"],
408457confirm: [],
409458});
410459411460await runConfigureWizard({ command: "configure", sections: ["channels"] }, createRuntime());
412461413462const setupChannelsCall = mocks.setupChannels.mock.calls[0] as Array<unknown> | undefined;
414463const setupChannelsConfig = requireRecord(setupChannelsCall?.[0], "setupChannels config");
415-expect(getGateway(setupChannelsConfig).mode).toBe("local");
464+expect(setupChannelsConfig.gateway).toBeUndefined();
416465const setupChannelsOptions = requireRecord(setupChannelsCall?.[3], "setupChannels options");
417466expect(setupChannelsOptions.deferStatusUntilSelection).toBe(true);
418467expect(setupChannelsOptions.skipStatusNote).toBe(true);
@@ -439,7 +488,7 @@ describe("runConfigureWizard", () => {
439488})(cfg),
440489);
441490queueWizardPrompts({
442-select: ["local"],
491+select: [],
443492confirm: [true, false],
444493});
445494@@ -461,7 +510,7 @@ describe("runConfigureWizard", () => {
461510},
462511});
463512queueWizardPrompts({
464-select: ["local", "cached"],
513+select: ["cached"],
465514confirm: [true, true, false, true],
466515});
467516@@ -527,7 +576,7 @@ describe("runConfigureWizard", () => {
527576};
528577setupBaseWizardState(baseConfig);
529578queueWizardPrompts({
530-select: ["local"],
579+select: [],
531580confirm: [],
532581});
533582此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。