Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ jobs:
include:
- platform: windows-latest
- platform: ubuntu-22.04
- platform: macos-latest

runs-on: ${{ matrix.platform }}

Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
include:
- platform: windows-latest
- platform: ubuntu-22.04
- platform: macos-latest

runs-on: ${{ matrix.platform }}

Expand All @@ -40,8 +41,37 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Import Apple code-signing certificate
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_P12_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
run: |
if [ -z "$APPLE_CERTIFICATE_P12_BASE64" ] || [ -z "$APPLE_CERTIFICATE_P12_PASSWORD" ]; then
echo "Missing Apple certificate secrets. Add APPLE_CERTIFICATE_P12_BASE64 and APPLE_CERTIFICATE_P12_PASSWORD."
exit 1
fi

KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
CERT_PATH="$RUNNER_TEMP/certificate.p12"

echo "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$CERT_PATH"

security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
security import "$CERT_PATH" -k build.keychain -P "$APPLE_CERTIFICATE_P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security find-identity -v -p codesigning build.keychain

- name: Build Electron app
run: npm run build
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
MAC_CODESIGN_IDENTITY: ${{ secrets.MAC_CODESIGN_IDENTITY }}

- name: Upload artifacts
uses: actions/upload-artifact@v4
Expand All @@ -67,5 +97,6 @@ jobs:
files: |
artifacts/**/*.exe
artifacts/**/*.AppImage
artifacts/**/*.dmg
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
generate_release_notes: true
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ server-components
out
.vite
build/installer.nsh
build/entitlements.mac.plist
Binary file added app-icon.icns
Binary file not shown.
12 changes: 12 additions & 0 deletions build/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
28 changes: 23 additions & 5 deletions electron/ipc/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ const log = getLogger('electron.settings')
const SETTINGS_FILENAME = 'settings.json'
const LEGACY_CONFIG_FILENAME = 'config.json'

// Apple Silicon has no CUDA, so the legacy `world_engine` backend can't even
// import there — the server advertises `quark` only (see manager.py's
// `IS_DARWIN_ARM64` gate in `supported_capabilities`). The client setting
// defaults to `world_engine` for every platform, so without this a Mac would
// try to launch the engine on a backend that can't load (and the
// capability-based clamp only kicks in *after* a server reports `/health`,
// which is too late for a standalone launch). Pin the setting to `quark` here
// so it propagates to the server via the InitRequest. Mirrors the server gate.
const IS_APPLE_SILICON = process.platform === 'darwin' && process.arch === 'arm64'

function normalizeForPlatform(settings: Settings): { settings: Settings; changed: boolean } {
if (IS_APPLE_SILICON && settings.engine_backend !== 'quark') {
return { settings: { ...settings, engine_backend: 'quark' }, changed: true }
}
return { settings, changed: false }
}

function getSettingsPath(): string {
const configDir = getConfigDir()
if (!fs.existsSync(configDir)) {
Expand Down Expand Up @@ -161,10 +178,11 @@ function loadSettings(settingsPath: string): { settings: Settings; dirty: boolea
export function readSettingsSync(): Settings {
const settingsPath = getSettingsPath()
const { settings, dirty } = loadSettings(settingsPath)
if (dirty) {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
const { settings: normalized, changed } = normalizeForPlatform(settings)
if (dirty || changed) {
fs.writeFileSync(settingsPath, JSON.stringify(normalized, null, 2))
}
return settings
return normalized
}

/** Env vars injected into any uv / python subprocess when offline mode is on.
Expand Down Expand Up @@ -195,12 +213,12 @@ export function registerSettingsIpc(): void {
})

ipcMain.handle('read-default-settings', () => {
return settingsSchema.parse({})
return normalizeForPlatform(settingsSchema.parse({})).settings
})

ipcMain.handle('write-settings', (_event, settings: Settings) => {
const settingsPath = getSettingsPath()
const validated = settingsSchema.parse(settings)
const validated = normalizeForPlatform(settingsSchema.parse(settings)).settings
fs.writeFileSync(settingsPath, JSON.stringify(validated, null, 2))
})

Expand Down
31 changes: 30 additions & 1 deletion forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,29 @@ import { dirname, resolve } from 'node:path'

const __dirname = dirname(fileURLToPath(import.meta.url))

const shouldSignMac =
process.platform === 'darwin' && (Boolean(process.env.CSC_LINK) || Boolean(process.env.MAC_CODESIGN_IDENTITY))

const shouldNotarizeMac =
shouldSignMac &&
Boolean(process.env.APPLE_ID) &&
Boolean(process.env.APPLE_APP_SPECIFIC_PASSWORD) &&
Boolean(process.env.APPLE_TEAM_ID)

const macNotarizeCredentials = shouldNotarizeMac
? {
appleId: process.env.APPLE_ID as string,
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD as string,
teamId: process.env.APPLE_TEAM_ID as string
}
: undefined

const config: ForgeConfig = {
packagerConfig: {
asar: true,
executableName: 'biome',
appBundleId: 'ai.overworld.biome',
appCategoryType: 'public.app-category.games',
icon: './app-icon',
appCopyright: 'Copyright © 2026 Overworld',
extraResource: [
Expand All @@ -23,7 +42,17 @@ const config: ForgeConfig = {
'./assets/9SALERNO.TTF',
'./app-icon.ico',
'./app-icon.png'
]
],
osxSign: shouldSignMac
? {
identity: process.env.MAC_CODESIGN_IDENTITY || undefined,
optionsForFile: () => ({
hardenedRuntime: true,
entitlements: 'build/entitlements.mac.plist'
})
}
: undefined,
osxNotarize: macNotarizeCredentials
},
makers: [
new MakerNSIS({
Expand Down
8 changes: 8 additions & 0 deletions server-components/engine/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,14 @@ async def load_engine(
backend_quant = _QUARK_QUANT_MAP[requested_quant]
except KeyError as e:
raise QuarkUnsupportedQuantError(requested_quant) from e
# Apple Silicon has no native fp8, so quark forces all-bf16
# regardless of what we pass. But quark reads `quant=None` as
# "default → fp8" and emits a RuntimeWarning when it rewrites
# that to bf16. We only advertise `Quant.NONE` on Metal anyway,
# so make the bf16 intent explicit to keep the engine logs
# clean (quark returns `"bf16"` unchanged, no warning).
if IS_DARWIN_ARM64 and backend_quant is None:
backend_quant = "bf16"
else:
backend_quant = requested_quant

Expand Down
Loading