mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(wizard): honor gateway overrides in manual flow (#122961)
This commit is contained in:
committed by
GitHub
parent
705f043e04
commit
5db09954c7
@@ -41,7 +41,8 @@ describe("configureGatewayForSetup", () => {
|
||||
return buildWizardPrompter({
|
||||
select,
|
||||
text: vi.fn(async (paramsLocal) => {
|
||||
const value = textQueue.shift() as string;
|
||||
const hasQueuedValue = textQueue.length > 0;
|
||||
const value = hasQueuedValue ? textQueue.shift() : paramsLocal.initialValue;
|
||||
const error = typeof value === "string" ? paramsLocal.validate?.(value) : undefined;
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
@@ -106,6 +107,56 @@ describe("configureGatewayForSetup", () => {
|
||||
expect(result.nextConfig.gateway?.nodes?.commands).toBeUndefined();
|
||||
});
|
||||
|
||||
it("seeds advanced gateway prompts from explicit classic options", async () => {
|
||||
const gatewayDefaults = resolveQuickstartGatewayDefaults(
|
||||
{},
|
||||
{
|
||||
gatewayPort: 19511,
|
||||
gatewayBind: "lan",
|
||||
gatewayAuth: "password",
|
||||
gatewayPassword: "manual-gateway-password-placeholder",
|
||||
tailscale: "off",
|
||||
},
|
||||
);
|
||||
const select = vi.fn(async (params: WizardSelectParams<unknown>) => {
|
||||
return params.initialValue ?? params.options[0]?.value;
|
||||
}) as unknown as WizardPrompter["select"];
|
||||
const text = vi.fn(async (params: { initialValue?: string }) => params.initialValue ?? "");
|
||||
const confirm = vi.fn(
|
||||
async (params: { initialValue?: boolean }) => params.initialValue ?? false,
|
||||
);
|
||||
const prompter = buildWizardPrompter({ select, text, confirm });
|
||||
|
||||
const result = await configureGatewayForSetup({
|
||||
flow: "advanced",
|
||||
baseConfig: {},
|
||||
nextConfig: {},
|
||||
localPort: gatewayDefaults.port,
|
||||
quickstartGateway: gatewayDefaults,
|
||||
prompter,
|
||||
runtime: createRuntime(),
|
||||
});
|
||||
|
||||
expect(text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Gateway port", initialValue: "19511" }),
|
||||
);
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Gateway bind address", initialValue: "lan" }),
|
||||
);
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Gateway access protection", initialValue: "password" }),
|
||||
);
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Tailscale exposure", initialValue: "off" }),
|
||||
);
|
||||
expect(result.nextConfig.gateway).toMatchObject({
|
||||
port: 19511,
|
||||
bind: "lan",
|
||||
auth: { mode: "password", password: "manual-gateway-password-placeholder" },
|
||||
tailscale: { mode: "off" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["1e3", "0x1000"])("rejects loose gateway port input: %s", async (port) => {
|
||||
mocks.randomToken.mockReturnValue("generated-token");
|
||||
|
||||
@@ -374,6 +425,42 @@ describe("configureGatewayForSetup", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("seeds an explicit env token ref into advanced gateway setup", async () => {
|
||||
const previous = process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = "token-from-env-ref";
|
||||
try {
|
||||
const gatewayDefaults = resolveQuickstartGatewayDefaults(
|
||||
{},
|
||||
{ gatewayPort: 19511, gatewayTokenRefEnv: "OPENCLAW_GATEWAY_TOKEN" },
|
||||
);
|
||||
const result = await configureGatewayForSetup({
|
||||
flow: "advanced",
|
||||
baseConfig: {},
|
||||
nextConfig: {},
|
||||
localPort: gatewayDefaults.port,
|
||||
quickstartGateway: gatewayDefaults,
|
||||
prompter: createPrompter({ selectQueue: [], textQueue: [] }),
|
||||
runtime: createRuntime(),
|
||||
});
|
||||
|
||||
expect(result.nextConfig.gateway?.auth).toEqual({
|
||||
mode: "token",
|
||||
token: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OPENCLAW_GATEWAY_TOKEN",
|
||||
},
|
||||
});
|
||||
expect(result.settings.gatewayToken).toBe("token-from-env-ref");
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
} else {
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = previous;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("persists classic quickstart overrides through gateway safety normalization", async () => {
|
||||
const password = ["classic", "gateway", "placeholder"].join("-");
|
||||
mocks.getTailnetHostname.mockResolvedValue("test-tailnet.ts.net");
|
||||
|
||||
@@ -120,6 +120,7 @@ export async function configureGatewayForSetup(
|
||||
hint: t("wizard.gateway.bindCustomHint"),
|
||||
},
|
||||
],
|
||||
initialValue: quickstartGateway.bind,
|
||||
});
|
||||
|
||||
let customBindHost = quickstartGateway.customBindHost;
|
||||
@@ -149,7 +150,7 @@ export async function configureGatewayForSetup(
|
||||
},
|
||||
{ value: "password", label: t("common.password") },
|
||||
],
|
||||
initialValue: "token",
|
||||
initialValue: quickstartGateway.authMode,
|
||||
})) as GatewayAuthChoice);
|
||||
|
||||
const tailscaleMode: GatewayWizardSettings["tailscaleMode"] =
|
||||
@@ -158,6 +159,7 @@ export async function configureGatewayForSetup(
|
||||
: await prompter.select<GatewayWizardSettings["tailscaleMode"]>({
|
||||
message: t("wizard.gateway.tailscaleExposure"),
|
||||
options: getLocalizedTailscaleExposureOptions(),
|
||||
initialValue: quickstartGateway.tailscaleMode,
|
||||
});
|
||||
|
||||
// Detect Tailscale binary before proceeding with serve/funnel setup.
|
||||
@@ -173,12 +175,12 @@ export async function configureGatewayForSetup(
|
||||
}
|
||||
}
|
||||
|
||||
let tailscaleResetOnExit = flow === "quickstart" ? quickstartGateway.tailscaleResetOnExit : false;
|
||||
let tailscaleResetOnExit = quickstartGateway.tailscaleResetOnExit;
|
||||
if (tailscaleMode !== "off" && flow !== "quickstart") {
|
||||
await prompter.note(t("wizard.gatewayTailscale.docsNote"), "Tailscale");
|
||||
tailscaleResetOnExit = await prompter.confirm({
|
||||
message: t("wizard.gateway.tailscaleReset"),
|
||||
initialValue: false,
|
||||
initialValue: tailscaleResetOnExit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,11 +209,10 @@ export async function configureGatewayForSetup(
|
||||
value: quickstartGateway.token,
|
||||
defaults: nextConfig.secrets?.defaults,
|
||||
}).ref;
|
||||
const tokenMode =
|
||||
flow === "quickstart" && opts.secretInputMode !== "ref" // pragma: allowlist secret
|
||||
? quickstartTokenRef
|
||||
const tokenMode = quickstartTokenRef
|
||||
? "ref"
|
||||
: "plaintext"
|
||||
: flow === "quickstart" && opts.secretInputMode !== "ref" // pragma: allowlist secret
|
||||
? "plaintext"
|
||||
: await resolveSecretInputModeForEnvSelection({
|
||||
prompter,
|
||||
explicitMode: opts.secretInputMode,
|
||||
@@ -224,7 +225,7 @@ export async function configureGatewayForSetup(
|
||||
},
|
||||
});
|
||||
if (tokenMode === "ref") {
|
||||
if (flow === "quickstart" && quickstartTokenRef) {
|
||||
if (quickstartTokenRef) {
|
||||
gatewayTokenInput = quickstartTokenRef;
|
||||
gatewayToken = await resolveSetupSecretInputString({
|
||||
config: nextConfig,
|
||||
@@ -280,8 +281,13 @@ export async function configureGatewayForSetup(
|
||||
}
|
||||
|
||||
if (authMode === "password") {
|
||||
const existingPassword = normalizeSecretInputString(quickstartGateway.password);
|
||||
const existingPasswordRef = resolveSecretInputRef({
|
||||
value: quickstartGateway.password,
|
||||
defaults: nextConfig.secrets?.defaults,
|
||||
}).ref;
|
||||
let password: SecretInput | undefined =
|
||||
flow === "quickstart" && quickstartGateway.password ? quickstartGateway.password : undefined;
|
||||
flow === "quickstart" ? quickstartGateway.password : (existingPasswordRef ?? undefined);
|
||||
if (!password) {
|
||||
const selectedMode = await resolveSecretInputModeForEnvSelection({
|
||||
prompter,
|
||||
@@ -305,7 +311,19 @@ export async function configureGatewayForSetup(
|
||||
});
|
||||
password = resolved.ref;
|
||||
} else {
|
||||
password = normalizeWizardTextInput(
|
||||
let passwordInput: string | undefined;
|
||||
if (existingPassword) {
|
||||
const keep = await prompter.confirm({
|
||||
message: t("wizard.gateway.existingPasswordConfirm", {
|
||||
password: maskApiKey(existingPassword),
|
||||
}),
|
||||
initialValue: true,
|
||||
});
|
||||
passwordInput = keep ? existingPassword : undefined;
|
||||
}
|
||||
password =
|
||||
passwordInput ??
|
||||
normalizeWizardTextInput(
|
||||
await prompter.text({
|
||||
message: t("wizard.gateway.passwordPrompt"),
|
||||
validate: validateGatewayPasswordInput,
|
||||
|
||||
@@ -2544,6 +2544,78 @@ describe("runSetupWizard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "explicit CLI gateway values",
|
||||
gatewayOptions: {
|
||||
gatewayPort: 19511,
|
||||
gatewayBind: "lan" as const,
|
||||
gatewayAuth: "password" as const,
|
||||
gatewayToken: "manual-gateway-token-placeholder",
|
||||
gatewayPassword: "manual-gateway-password-placeholder",
|
||||
tailscale: "off" as const,
|
||||
tailscaleResetOnExit: false,
|
||||
},
|
||||
expectedPort: 19511,
|
||||
expectedProbeAuth: {
|
||||
token: "manual-gateway-token-placeholder",
|
||||
password: "manual-gateway-password-placeholder",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "derived port when gateway values are omitted",
|
||||
gatewayOptions: {},
|
||||
expectedPort: 18789,
|
||||
expectedProbeAuth: {},
|
||||
},
|
||||
])(
|
||||
"uses the $label for the manual probe and port prompt",
|
||||
async ({ gatewayOptions, expectedPort, expectedProbeAuth }) => {
|
||||
const prompter = buildWizardPrompter({});
|
||||
const runtime = createRuntime();
|
||||
|
||||
await runSetupWizard(
|
||||
{
|
||||
acceptRisk: true,
|
||||
flow: "advanced",
|
||||
mode: "local",
|
||||
authChoice: "skip",
|
||||
...gatewayOptions,
|
||||
installDaemon: false,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
skipSearch: true,
|
||||
skipHealth: true,
|
||||
skipUi: true,
|
||||
},
|
||||
runtime,
|
||||
prompter,
|
||||
);
|
||||
|
||||
expectRecordFields(
|
||||
getMockCallArg(probeGatewayReachable, 0, 0, "gateway probe"),
|
||||
{ url: `ws://127.0.0.1:${expectedPort}`, ...expectedProbeAuth },
|
||||
"gateway probe params",
|
||||
);
|
||||
const gatewaySetup = expectRecordFields(
|
||||
getMockCallArg(configureGatewayForSetup, 0, 0, "gateway setup"),
|
||||
{ localPort: expectedPort },
|
||||
"gateway setup params",
|
||||
);
|
||||
if (gatewayOptions.gatewayPort !== undefined) {
|
||||
expect(gatewaySetup.quickstartGateway).toMatchObject({
|
||||
port: 19511,
|
||||
bind: "lan",
|
||||
authMode: "password",
|
||||
token: "manual-gateway-token-placeholder",
|
||||
password: "manual-gateway-password-placeholder",
|
||||
tailscaleMode: "off",
|
||||
tailscaleResetOnExit: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("passes secretInputMode through to local gateway config step", async () => {
|
||||
configureGatewayForSetup.mockClear();
|
||||
const prompter = buildWizardPrompter({});
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { resolveOnboardingAgentTarget } from "../commands/onboard-agent-target.js";
|
||||
import type { GatewayAuthChoice, OnboardMode, OnboardOptions } from "../commands/onboard-types.js";
|
||||
import { hasResolvedRosterBeforeMigrations } from "../config/agent-roster-provenance.js";
|
||||
import { ConfigMutationConflictError, resolveGatewayPort } from "../config/config.js";
|
||||
import { ConfigMutationConflictError } from "../config/config.js";
|
||||
import { createMergePatch } from "../config/merge-patch.js";
|
||||
import { applyMergePatch } from "../config/merge-patch.js";
|
||||
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
|
||||
@@ -289,7 +289,7 @@ async function runSetupWizardOnce(
|
||||
|
||||
const quickstartGateway: QuickstartGatewayDefaults = resolveQuickstartGatewayDefaults(
|
||||
baseConfig,
|
||||
wizardFlow === "quickstart" ? opts : undefined,
|
||||
opts,
|
||||
);
|
||||
|
||||
if (flow === "quickstart") {
|
||||
@@ -341,13 +341,13 @@ async function runSetupWizardOnce(
|
||||
await prompter.note(quickstartLines.join("\n"), "QuickStart");
|
||||
}
|
||||
|
||||
const localPort = resolveGatewayPort(baseConfig);
|
||||
const localPort = quickstartGateway.port;
|
||||
const localUrl = `ws://127.0.0.1:${localPort}`;
|
||||
let localGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
try {
|
||||
const resolvedGatewayToken = await resolveSetupSecretInputString({
|
||||
config: baseConfig,
|
||||
value: baseConfig.gateway?.auth?.token,
|
||||
value: quickstartGateway.token,
|
||||
path: "gateway.auth.token",
|
||||
env: process.env,
|
||||
});
|
||||
@@ -367,7 +367,7 @@ async function runSetupWizardOnce(
|
||||
try {
|
||||
const resolvedGatewayPassword = await resolveSetupSecretInputString({
|
||||
config: baseConfig,
|
||||
value: baseConfig.gateway?.auth?.password,
|
||||
value: quickstartGateway.password,
|
||||
path: "gateway.auth.password",
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user