# v0.4.55 (2026-05-18)

## Features
- Xiaomi MiMo Token Plan: region selector (Singapore / China / Europe) — keys are cluster-specific
- Antigravity: risk confirmation dialog before first connection
- Gemini CLI: surface upstream retry delay on 429 errors

## Fixes
- MITM: cannot kill process on macOS under sudo (lsof not found in PATH)
- Stream: false-positive stall timeout on Claude reasoning / Kiro responses
- Tunnel: cannot re-enable after disable (stuck state)
- Tunnel: cloudflared error messages now include log tail for easier debugging
- Language switcher: applies selected locale immediately on close (#1234)
- Antigravity OAuth: metadata now matches the official client

## Improvements
- Gemini CLI: bump engine to 0.34.0
- Re-hide `qwen` (OAuth EOL) and `iflow` (not ready) providers
This commit is contained in:
decolua
2026-05-18 16:26:35 +07:00
parent 90a47c3f29
commit 613a0a819a
12 changed files with 125 additions and 47 deletions
+12 -12
View File
@@ -197,8 +197,8 @@ function killCloudflaredByAppPort(appPort) {
function killAllAppProcesses(appPort) {
return new Promise((resolve) => {
try {
// Kill MITM first (admin/sudo process, needs special handling)
killMitmByPidFile();
// Kill MIT first (privileged process, needs special handling)
killProxyByPidFile();
// Kill cloudflared/tailscale by PID file (precise, only this app's tunnel)
killTunnelByPidFile();
@@ -305,13 +305,13 @@ function waitForExit(pid, timeoutMs) {
return false;
}
// Kill MITM server by PID file (MITM runs as admin/sudo, needs special handling)
// Sends SIGTERM first so MITM can clean up /etc/hosts entries before dying.
function killMitmByPidFile() {
// Kill MIT server by PID file (runs privileged, needs special handling)
// Sends SIGTERM first so MIT can clean up host entries before dying.
function killProxyByPidFile() {
try {
const mitmPidFile = path.join(getAppDataDir(), "mitm", ".mitm.pid");
if (!fs.existsSync(mitmPidFile)) return;
const pid = parseInt(fs.readFileSync(mitmPidFile, "utf8").trim(), 10);
const pidFile = path.join(getAppDataDir(), "mitm", ".mitm.pid");
if (!fs.existsSync(pidFile)) return;
const pid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
if (!pid) return;
if (process.platform === "win32") {
@@ -333,7 +333,7 @@ function killMitmByPidFile() {
catch { try { process.kill(pid, "SIGKILL"); } catch { } }
}
}
try { fs.unlinkSync(mitmPidFile); } catch { }
try { fs.unlinkSync(pidFile); } catch { }
} catch { }
}
@@ -584,8 +584,8 @@ function startServer(latestVersion) {
const { killTray } = require("./src/cli/tray/tray");
killTray();
} catch (e) { }
// Kill MITM server (admin/sudo process) via PID file
killMitmByPidFile();
// Kill MIT server (privileged process) via PID file
killProxyByPidFile();
// Kill cloudflared/tailscale via PID file (only this app's tunnel)
killTunnelByPidFile();
// Kill server process directly
@@ -772,7 +772,7 @@ function startServer(latestVersion) {
if (aliveMs >= RESTART_RESET_MS) restartCount = 0;
if (restartCount >= MAX_RESTARTS) {
console.error(`\n⚠️ Server crashed ${MAX_RESTARTS} times. Disabling MITM and restarting...`);
console.error(`\n⚠️ Server crashed ${MAX_RESTARTS} times. Disabling MIT and restarting...`);
try {
const dbPath = path.join(os.homedir(), process.platform === "win32" ? path.join("AppData", "Roaming", "9router", "db.json") : path.join(".9router", "db.json"));
if (fs.existsSync(dbPath)) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "9router",
"version": "0.4.52",
"version": "0.4.55",
"description": "9Router CLI - Start and manage 9Router server",
"bin": {
"9router": "./cli.js"