mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(onboard): skip Homebrew prompt on unsupported platforms (#93521)
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
This commit is contained in:
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Onboarding/skills: show the Homebrew install recommendation only on macOS and Linux, so FreeBSD and other unsupported platforms no longer get a misleading brew prompt. Fixes #68893; carries forward #68894, #68910, #68941, #68943, #69002, and #69545. Thanks @yurivict, @Sanjays2402, @Eruditi, @JustInCache, @nnish16, and @Mlightsnow.
|
||||
- Channels and delivery: preserve account-scoped DM channel send policy, rich Telegram final replies, rich Telegram tables and lists, Telegram thread-create CLI remapping, Slack outbound `message_sent` hooks, contributed message-tool schema optionality, same-channel generated media completions, and channel chunking around surrogate pairs and Infinity limits. (#92788, #92679, #89421, #89943, #91137, #91246, #92735) Thanks @yetval, @obviyus, @spacegeologist, @rishitamrakar, @lundog, @TurboTheTurtle, and @yhterrance.
|
||||
- iMessage: normalize leading NUL sent-message echo prefixes while preserving interior NUL bytes and the leading attributedBody marker handling from #73942. Carries forward #63581. Thanks @drvoss.
|
||||
- Discord: give generated auto-thread titles a 60-second timeout and 4,096-token reasoning-model output budget, clamped to the selected model output cap. (#64734) Thanks @hanamizuki.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Onboard skills tests cover skill setup prompts, package manager config, and skip behavior.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
@@ -143,16 +143,30 @@ const runtime: RuntimeEnv = {
|
||||
}) as RuntimeEnv["exit"],
|
||||
};
|
||||
|
||||
const supportsHomebrewPrompt = process.platform === "darwin" || process.platform === "linux";
|
||||
|
||||
async function withPlatform<T>(platform: NodeJS.Platform, fn: () => Promise<T>): Promise<T> {
|
||||
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!;
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
value: platform,
|
||||
});
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", originalPlatformDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
describe("setupSkills", () => {
|
||||
afterEach(() => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.isContainerEnvironment.mockReset();
|
||||
mocks.resolveBrewExecutable.mockReset();
|
||||
});
|
||||
|
||||
it("hides brew-only installs in Linux containers when brew is missing", async () => {
|
||||
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!;
|
||||
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
|
||||
try {
|
||||
await withPlatform("linux", async () => {
|
||||
mockMissingBrewStatus([
|
||||
createBundledSkill({
|
||||
name: "video-frames",
|
||||
@@ -173,15 +187,11 @@ describe("setupSkills", () => {
|
||||
expect(
|
||||
notes.find((n) => n.message.includes("No missing skill dependencies to install")),
|
||||
).toBeUndefined();
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", originalPlatformDescriptor);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps brew-only installs visible when Linuxbrew is resolved off PATH", async () => {
|
||||
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!;
|
||||
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
|
||||
try {
|
||||
await withPlatform("linux", async () => {
|
||||
mockMissingBrewStatus([
|
||||
createBundledSkill({
|
||||
name: "video-frames",
|
||||
@@ -202,13 +212,11 @@ describe("setupSkills", () => {
|
||||
);
|
||||
expect(notes.find((n) => n.title === "Container skill installs")).toBeUndefined();
|
||||
expect(notes.find((n) => n.title === "Homebrew recommended")).toBeUndefined();
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", originalPlatformDescriptor);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("does not recommend Homebrew when user skips installing brew-backed deps", async () => {
|
||||
if (process.platform === "win32") {
|
||||
if (!supportsHomebrewPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -247,7 +255,7 @@ describe("setupSkills", () => {
|
||||
});
|
||||
|
||||
it("recommends Homebrew when user selects a brew-backed install and brew is missing", async () => {
|
||||
if (process.platform === "win32") {
|
||||
if (!supportsHomebrewPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,4 +287,24 @@ describe("setupSkills", () => {
|
||||
expect(emptyStateNote?.message).toContain("openclaw skills list --verbose");
|
||||
expect(emptyStateNote?.message).toContain("openclaw skills check");
|
||||
});
|
||||
|
||||
it("does not recommend Homebrew on FreeBSD", async () => {
|
||||
await withPlatform("freebsd", async () => {
|
||||
mockMissingBrewStatus([
|
||||
createBundledSkill({
|
||||
name: "video-frames",
|
||||
description: "ffmpeg",
|
||||
bins: ["ffmpeg"],
|
||||
installLabel: "Install ffmpeg (brew)",
|
||||
}),
|
||||
]);
|
||||
|
||||
const { prompter, notes } = createPrompter({ multiselect: ["video-frames"] });
|
||||
await setupSkills({} as OpenClawConfig, "/tmp/ws", runtime, prompter);
|
||||
|
||||
const brewNote = notes.find((n) => n.title === "Homebrew recommended");
|
||||
expect(brewNote).toBeUndefined();
|
||||
expect(mocks.detectBinary).not.toHaveBeenCalledWith("brew");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,12 @@ import { t } from "../wizard/i18n/index.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js";
|
||||
|
||||
const HOMEBREW_PROMPT_PLATFORMS = new Set(["darwin", "linux"]);
|
||||
|
||||
function supportsHomebrewPrompt(platform: NodeJS.Platform): boolean {
|
||||
return HOMEBREW_PROMPT_PLATFORMS.has(platform);
|
||||
}
|
||||
|
||||
function summarizeInstallFailure(message: string): string | undefined {
|
||||
const cleaned = message.replace(/^Install failed(?:\s*\([^)]*\))?\s*:?\s*/i, "").trim();
|
||||
if (!cleaned) {
|
||||
@@ -145,7 +151,7 @@ export async function setupSkills(
|
||||
.filter((item): item is (typeof installable)[number] => Boolean(item));
|
||||
|
||||
const needsBrewPrompt =
|
||||
process.platform !== "win32" &&
|
||||
supportsHomebrewPrompt(process.platform) &&
|
||||
selectedSkills.some((skill) => skill.install.some((option) => option.kind === "brew")) &&
|
||||
!(await detectBrewOnce());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user