mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
fix(onboard): give actionable managed gateway recovery (#124783)
* fix(onboard): keep managed gateway recovery on service path Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * fix(onboard): distinguish install and restart recovery Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * fix(onboard): localize gateway recovery guidance Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * style(onboard): format gateway recovery test Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * chore(ci): refresh service recovery validation --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
515fe51d8c
commit
54cbce25bd
@@ -1146,6 +1146,10 @@ export const en = {
|
||||
healthCheckHelp: "Health check help",
|
||||
installGateway: "Install Gateway service (recommended)",
|
||||
laterTitle: "Later",
|
||||
managedGatewaySetupFailed:
|
||||
"The managed {service} setup failed: {error}\nInspect service state and logs: {statusCommand}\nRetry the managed service installation: {recoveryCommand}",
|
||||
managedGatewayUnreachable:
|
||||
"The managed {service} did not become reachable after setup.\nInspect service state and logs: {statusCommand}\nRetry the managed service: {recoveryCommand}",
|
||||
managedWebSearchSkipped: "Managed web search provider was skipped.",
|
||||
noBackgroundGatewayExpected:
|
||||
"Setup was run without Gateway service install, so no background gateway is expected.",
|
||||
|
||||
@@ -1108,6 +1108,10 @@ export const zh_CN = {
|
||||
healthCheckHelp: "健康检查帮助",
|
||||
installGateway: "安装 Gateway 服务(推荐)",
|
||||
laterTitle: "稍后",
|
||||
managedGatewaySetupFailed:
|
||||
"托管的 {service} 设置失败:{error}\n检查服务状态和日志:{statusCommand}\n重试托管服务安装:{recoveryCommand}",
|
||||
managedGatewayUnreachable:
|
||||
"托管的 {service} 在设置后仍无法访问。\n检查服务状态和日志:{statusCommand}\n重试托管服务:{recoveryCommand}",
|
||||
managedWebSearchSkipped: "已跳过托管 web search provider。",
|
||||
noBackgroundGatewayExpected: "本次设置未安装 Gateway 服务,因此不会有后台 Gateway。",
|
||||
noModelAuth: "提供商 “{provider}” 尚未配置凭据,聊天将失败,直到添加认证。",
|
||||
|
||||
@@ -1109,6 +1109,10 @@ export const zh_TW = {
|
||||
healthCheckHelp: "健康檢查說明",
|
||||
installGateway: "安裝 Gateway 服務(建議)",
|
||||
laterTitle: "稍後",
|
||||
managedGatewaySetupFailed:
|
||||
"受管理的 {service} 設定失敗:{error}\n檢查服務狀態和日誌:{statusCommand}\n重試受管理服務安裝:{recoveryCommand}",
|
||||
managedGatewayUnreachable:
|
||||
"受管理的 {service} 在設定後仍無法存取。\n檢查服務狀態和日誌:{statusCommand}\n重試受管理服務:{recoveryCommand}",
|
||||
managedWebSearchSkipped: "已略過託管 web search provider。",
|
||||
noBackgroundGatewayExpected: "本次設定未安裝 Gateway 服務,因此不會有背景 Gateway。",
|
||||
noModelAuth: "提供商「{provider}」尚未設定憑證,聊天將失敗,直到新增認證。",
|
||||
|
||||
@@ -231,6 +231,7 @@ vi.mock("../daemon/service.js", () => ({
|
||||
issues.map((issue) => issue.message).join("; "),
|
||||
startGatewayService,
|
||||
resolveGatewayService: vi.fn(() => ({
|
||||
label: "Mock Platform Service",
|
||||
isLoaded: gatewayServiceIsLoaded,
|
||||
restart: gatewayServiceRestart,
|
||||
uninstall: gatewayServiceUninstall,
|
||||
@@ -1297,8 +1298,48 @@ describe("finalizeSetupWizard", () => {
|
||||
expectNoteContains(prompter, "service install exploded", "Gateway");
|
||||
expectNoteContains(prompter, "Gateway: not detected (service install exploded)", "Control UI");
|
||||
expect(prompter.outro).toHaveBeenCalledWith(
|
||||
"Gateway not detected yet. Start now: openclaw gateway run",
|
||||
expect.stringContaining("managed Mock Platform Service setup failed"),
|
||||
);
|
||||
expectNoteContains(prompter, "openclaw gateway status --deep", "Gateway");
|
||||
expectNoteContains(prompter, "openclaw gateway install --force", "Gateway");
|
||||
expectNoteNotContains(prompter, "openclaw gateway run");
|
||||
expectNoteNotContains(prompter, "openclaw gateway restart");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["readiness timeout", "gateway readiness timed out"],
|
||||
["service crash", "gateway closed (1006 abnormal closure)"],
|
||||
["occupied port", "listen EADDRINUSE: address already in use 127.0.0.1:18789"],
|
||||
])("keeps managed %s recovery on the canonical service path", async (_name, detail) => {
|
||||
waitForGatewayReachable.mockResolvedValue({ ok: false, detail });
|
||||
probeGatewayReachable.mockResolvedValue({ ok: false, detail });
|
||||
const prompter = createLaterPrompter();
|
||||
const args = createAdvancedFinalizeArgs({ installDaemon: true, prompter });
|
||||
|
||||
await finalizeSetupWizard({ ...args, opts: { ...args.opts, skipHealth: false } });
|
||||
|
||||
expectNoteContains(prompter, "managed Mock Platform Service", "Gateway");
|
||||
expectNoteContains(prompter, "openclaw gateway status --deep", "Gateway");
|
||||
expectNoteContains(prompter, "openclaw gateway restart", "Gateway");
|
||||
expectNoteNotContains(prompter, "openclaw gateway run");
|
||||
expectNoteNotContains(prompter, "openclaw onboard --install-daemon");
|
||||
expectNoteNotContains(prompter, "openclaw gateway install --force");
|
||||
});
|
||||
|
||||
it("localizes managed service recovery at the finalize boundary", async () => {
|
||||
await withEnvAsync({ OPENCLAW_LOCALE: "zh-CN" }, async () => {
|
||||
waitForGatewayReachable.mockResolvedValue({ ok: false, detail: "readiness timed out" });
|
||||
probeGatewayReachable.mockResolvedValue({ ok: false, detail: "readiness timed out" });
|
||||
const prompter = createLaterPrompter();
|
||||
const args = createAdvancedFinalizeArgs({ installDaemon: true, prompter });
|
||||
|
||||
await finalizeSetupWizard({ ...args, opts: { ...args.opts, skipHealth: false } });
|
||||
|
||||
expectNoteContains(prompter, "托管的 Mock Platform Service 在设置后仍无法访问", "Gateway");
|
||||
expectNoteContains(prompter, "检查服务状态和日志", "Gateway");
|
||||
expectNoteContains(prompter, "openclaw gateway restart", "Gateway");
|
||||
expectNoteNotContains(prompter, "openclaw gateway run");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns an authoritative failed outcome when gateway installation fails", async () => {
|
||||
|
||||
@@ -163,19 +163,47 @@ export type GatewayServiceSetupOutcome =
|
||||
| { status: "skipped"; reason: "explicit" | "systemd-unavailable" | "external" }
|
||||
| { status: "failed"; error: string };
|
||||
|
||||
function buildGatewayRecoveryProjection(gateway: GatewayServiceSetupOutcome): {
|
||||
function buildGatewayRecoveryProjection(params: {
|
||||
gateway: GatewayServiceSetupOutcome;
|
||||
reachable: boolean;
|
||||
serviceLabel?: string;
|
||||
}): {
|
||||
detail: string;
|
||||
summary: string;
|
||||
} {
|
||||
const { gateway } = params;
|
||||
const notDetected = t("wizard.finalize.gatewayNotDetected");
|
||||
if (params.reachable) {
|
||||
return { detail: t("wizard.finalize.gatewayReachable"), summary: t("wizard.guided.complete") };
|
||||
}
|
||||
if (gateway.status === "ready") {
|
||||
const service = params.serviceLabel ?? t("wizard.finalize.gatewayService");
|
||||
const detail = t("wizard.finalize.managedGatewayUnreachable", {
|
||||
service,
|
||||
statusCommand: formatCliCommand("openclaw gateway status --deep"),
|
||||
recoveryCommand: formatCliCommand("openclaw gateway restart"),
|
||||
});
|
||||
return { detail, summary: `${notDetected} ${detail.replaceAll("\n", " ")}` };
|
||||
}
|
||||
if (gateway.status === "failed") {
|
||||
const service = params.serviceLabel ?? t("wizard.finalize.gatewayService");
|
||||
const detail = t("wizard.finalize.managedGatewaySetupFailed", {
|
||||
service,
|
||||
error: gateway.error,
|
||||
statusCommand: formatCliCommand("openclaw gateway status --deep"),
|
||||
recoveryCommand: formatCliCommand("openclaw gateway install --force"),
|
||||
});
|
||||
return { detail, summary: `${notDetected} ${detail.replaceAll("\n", " ")}` };
|
||||
}
|
||||
|
||||
const startGuidance =
|
||||
gateway.status === "skipped" && gateway.reason === "external"
|
||||
gateway.reason === "external"
|
||||
? formatExternalSupervisorActionRequired("start the gateway")
|
||||
: t("wizard.finalize.startGatewayNow", {
|
||||
command: formatCliCommand("openclaw gateway run"),
|
||||
});
|
||||
const notDetected = t("wizard.finalize.gatewayNotDetected");
|
||||
const summary = [notDetected, startGuidance].join(" ");
|
||||
if (gateway.status === "skipped" && gateway.reason === "external") {
|
||||
if (gateway.reason === "external") {
|
||||
return { detail: [notDetected, startGuidance].join("\n"), summary };
|
||||
}
|
||||
return {
|
||||
@@ -492,7 +520,6 @@ export async function finalizeSetupWizard(
|
||||
prompter,
|
||||
runtime,
|
||||
});
|
||||
const gatewayRecovery = buildGatewayRecoveryProjection(gateway);
|
||||
if (gateway.status === "failed") {
|
||||
gatewayProbe = { ok: false, detail: gateway.error };
|
||||
}
|
||||
@@ -599,8 +626,19 @@ export async function finalizeSetupWizard(
|
||||
].join("\n"),
|
||||
t("wizard.finalize.healthCheckHelp"),
|
||||
);
|
||||
await prompter.note(
|
||||
buildGatewayRecoveryProjection({
|
||||
gateway,
|
||||
reachable: false,
|
||||
serviceLabel: resolveGatewayService().label,
|
||||
}).detail,
|
||||
"Gateway",
|
||||
);
|
||||
} else {
|
||||
await prompter.note(gatewayRecovery.detail, "Gateway");
|
||||
await prompter.note(
|
||||
buildGatewayRecoveryProjection({ gateway, reachable: false }).detail,
|
||||
"Gateway",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +1004,11 @@ export async function finalizeSetupWizard(
|
||||
}),
|
||||
].join(" ")
|
||||
: t("wizard.guided.complete")
|
||||
: gatewayRecovery.summary,
|
||||
: buildGatewayRecoveryProjection({
|
||||
gateway,
|
||||
reachable: false,
|
||||
serviceLabel: gateway.status === "skipped" ? undefined : resolveGatewayService().label,
|
||||
}).summary,
|
||||
);
|
||||
|
||||
if (shouldLaunchTui) {
|
||||
|
||||
Reference in New Issue
Block a user