From a1599fb32c0e78f5effacb355959d898e14b0f69 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Sun, 12 Apr 2026 21:52:01 -0500 Subject: [PATCH 1/2] Add language switcher and stabilize tool versions - Add locale selection to settings and translate key UI strings - Make sidebar cookie writes tolerant of missing cookieStore - Pin package typecheck, fmt, and lint commands to Bunx wrappers --- apps/desktop/package.json | 2 +- apps/marketing/package.json | 2 +- apps/mobile/package.json | 2 +- apps/server/package.json | 2 +- apps/web/package.json | 2 +- .../settings/SettingsRouteContext.tsx | 1 + .../src/components/settings/SettingsShell.tsx | 6 +- apps/web/src/components/ui/sidebar.tsx | 8 +- apps/web/src/routes/__root.tsx | 19 ++-- apps/web/src/routes/_chat.settings.index.tsx | 93 +++++++++++++++---- package.json | 8 +- packages/contracts/package.json | 2 +- packages/contracts/src/orchestration.ts | 4 +- packages/shared/package.json | 2 +- 14 files changed, 113 insertions(+), 40 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 1acc35d37..fe8e749a3 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -9,7 +9,7 @@ "dev:electron": "bun run scripts/dev-electron.mjs", "build": "tsdown", "start": "bun run scripts/start-electron.mjs", - "typecheck": "tsc --noEmit", + "typecheck": "bunx tsc@5.7.3 --noEmit", "test": "vitest run --passWithNoTests", "smoke-test": "node scripts/smoke-test.mjs", "release-smoke": "node ../../scripts/release-smoke.ts" diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 882effbd3..78bddc3c4 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -10,7 +10,7 @@ "preview": "next start", "start": "next start", "lint": "eslint .", - "typecheck": "tsc --noEmit" + "typecheck": "bunx tsc@5.7.3 --noEmit" }, "dependencies": { "@hookform/resolvers": "^3.10.0", diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 87bfb8299..caa5993ca 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "bun run scripts/build-mobile-shell.mjs", - "typecheck": "tsc --noEmit", + "typecheck": "bunx tsc@5.7.3 --noEmit", "test": "vitest run --passWithNoTests", "sync": "cap sync", "open:ios": "cap open ios", diff --git a/apps/server/package.json b/apps/server/package.json index d9801b1fb..5ee5f7a6d 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -19,7 +19,7 @@ "build": "node scripts/cli.ts build", "start": "node dist/index.mjs", "prepare": "node -e \"process.exit(process.env.CI ? 0 : 1)\" || (node ../../scripts/patch-effect-language-service.ts && node ../../scripts/patch-effect-smol-peer-installs.mjs)", - "typecheck": "tsc --noEmit", + "typecheck": "bunx tsc@5.7.3 --noEmit", "test": "vitest run" }, "dependencies": { diff --git a/apps/web/package.json b/apps/web/package.json index 68cbcbada..f9fd93111 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,7 +8,7 @@ "build": "vite build", "prepare": "node -e \"process.exit(process.env.CI ? 0 : 1)\" || node ../../scripts/patch-effect-language-service.ts", "preview": "vite preview", - "typecheck": "tsc --noEmit", + "typecheck": "bunx tsc@5.7.3 --noEmit", "test": "vitest run --passWithNoTests", "test:browser": "node ../../scripts/run-browser-tests.mjs vitest.browser.config.ts", "test:browser:install": "playwright install --with-deps chromium" diff --git a/apps/web/src/components/settings/SettingsRouteContext.tsx b/apps/web/src/components/settings/SettingsRouteContext.tsx index b83e77fd9..0967140d1 100644 --- a/apps/web/src/components/settings/SettingsRouteContext.tsx +++ b/apps/web/src/components/settings/SettingsRouteContext.tsx @@ -106,6 +106,7 @@ export function SettingsRouteContextProvider({ children }: { children: ReactNode ? ["PR request changes button"] : []), ...(settings.timestampFormat !== defaults.timestampFormat ? ["Time format"] : []), + ...(settings.locale !== defaults.locale ? ["Language"] : []), ...(settings.showStitchBorder !== defaults.showStitchBorder ? ["Stitch border"] : []), ...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming ? ["Assistant output"] diff --git a/apps/web/src/components/settings/SettingsShell.tsx b/apps/web/src/components/settings/SettingsShell.tsx index ede0e4555..eabba642f 100644 --- a/apps/web/src/components/settings/SettingsShell.tsx +++ b/apps/web/src/components/settings/SettingsShell.tsx @@ -17,6 +17,7 @@ import { Button } from "../ui/button"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { SidebarInset, SidebarTrigger } from "../ui/sidebar"; import { cn } from "../../lib/utils"; +import { useT } from "../../i18n/useI18n"; export type SettingsSectionId = | "general" @@ -104,6 +105,7 @@ export function SettingsShell({ children: ReactNode; }) { const navigate = useNavigate(); + const { t } = useT(); const activeItemLabel = useMemo( () => SETTINGS_NAV_ITEMS.find((item) => item.id === activeItem)?.label ?? "Settings", [activeItem], @@ -142,7 +144,7 @@ export function SettingsShell({ disabled={changedSettingLabels.length === 0} onClick={() => void onRestoreDefaults()} > - Restore defaults + {t("common.actions.restoreDefaults")} @@ -163,7 +165,7 @@ export function SettingsShell({ disabled={changedSettingLabels.length === 0} onClick={() => void onRestoreDefaults()} > - Restore defaults + {t("common.actions.restoreDefaults")} diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 1c4fa8e06..063b5868b 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -38,6 +38,10 @@ type SidebarContextProps = { toggleSidebar: () => void; }; +type CookieStoreLike = { + set(options: { expires: number; name: string; path: string; value: string }): Promise; +}; + type SidebarResizableOptions = { maxWidth?: number; minWidth?: number; @@ -147,7 +151,9 @@ function SidebarProvider({ } // This sets the cookie to keep the sidebar state. - await cookieStore.set({ + const cookieStore = (globalThis as typeof globalThis & { cookieStore?: CookieStoreLike }) + .cookieStore; + await cookieStore?.set({ expires: Date.now() + SIDEBAR_COOKIE_MAX_AGE * 1000, name: SIDEBAR_COOKIE_NAME, path: "/", diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index cc3b14515..6c37aec6f 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -33,6 +33,7 @@ import { MobileConnectionBanner } from "../components/mobile/MobileConnectionBan import { MobilePairingScreen } from "../components/mobile/MobilePairingScreen"; import { useMobilePairingState } from "../hooks/useMobilePairingState"; import { I18nProvider } from "../i18n/I18nProvider"; +import { useT } from "../i18n/useI18n"; import { VoodooStitches } from "../components/VoodooStitches"; export const Route = createRootRouteWithContext<{ @@ -57,13 +58,16 @@ function RootRouteView() { } function RootRouteContent() { + const { t } = useT(); const { isMobileShell, isLoading, pairingState } = useMobilePairingState(); if (isMobileShell && isLoading) { return (
-

Restoring mobile pairing...

+

+ {t("root.loading.restoringMobilePairing")} +

); @@ -78,7 +82,7 @@ function RootRouteContent() {

- Connecting to {APP_DISPLAY_NAME} server... + {t("root.loading.connectingServer", { appName: APP_DISPLAY_NAME })}

@@ -107,6 +111,7 @@ function RootRouteErrorView({ error, reset }: ErrorComponentProps) { } function RootRouteErrorContent({ error, reset }: ErrorComponentProps) { + const { t } = useT(); const message = errorMessage(error); const details = errorDetails(error); @@ -122,23 +127,23 @@ function RootRouteErrorContent({ error, reset }: ErrorComponentProps) { {APP_DISPLAY_NAME}

- Something went wrong. + {t("root.error.title")}

{message}

- Show error details - Hide error details + {t("root.error.showDetails")} + {t("root.error.hideDetails")}
             {details}
diff --git a/apps/web/src/routes/_chat.settings.index.tsx b/apps/web/src/routes/_chat.settings.index.tsx
index 8cc09865f..2db0bdde6 100644
--- a/apps/web/src/routes/_chat.settings.index.tsx
+++ b/apps/web/src/routes/_chat.settings.index.tsx
@@ -77,12 +77,19 @@ import {
   getProviderStatusDescription,
   getProviderStatusHeading,
 } from "../components/chat/providerStatusPresentation";
+import { APP_LOCALE_PREFERENCES } from "../i18n/types";
+import { useT } from "../i18n/useI18n";
 
-const TIMESTAMP_FORMAT_LABELS = {
-  locale: "System default",
-  "12-hour": "12-hour",
-  "24-hour": "24-hour",
-} as const;
+const TIMESTAMP_FORMAT_OPTIONS = [
+  { value: "locale", labelKey: "settings.general.timeFormat.option.locale" },
+  { value: "12-hour", labelKey: "settings.general.timeFormat.option.12Hour" },
+  { value: "24-hour", labelKey: "settings.general.timeFormat.option.24Hour" },
+] as const;
+
+const LANGUAGE_OPTIONS = APP_LOCALE_PREFERENCES.map((value) => ({
+  value,
+  labelKey: `settings.general.language.option.${value}` as const,
+}));
 
 const PR_REVIEW_REQUEST_CHANGES_TONE_OPTIONS: ReadonlyArray<{
   value: PrReviewRequestChangesTone;
@@ -435,6 +442,7 @@ function BuildInfoBlock({ label, buildInfo }: { label: string; buildInfo: BuildM
 }
 
 function SettingsRouteView() {
+  const { t } = useT();
   const navigate = useNavigate();
   const {
     settingsState: { settings, defaults, updateSettings },
@@ -709,6 +717,15 @@ function SettingsRouteView() {
     [settings, updateSettings],
   );
 
+  const languageOptionLabel = (value: (typeof APP_LOCALE_PREFERENCES)[number]) =>
+    t(`settings.general.language.option.${value}`);
+
+  const timestampFormatOptionLabel = (value: (typeof TIMESTAMP_FORMAT_OPTIONS)[number]["value"]) =>
+    t(
+      TIMESTAMP_FORMAT_OPTIONS.find((option) => option.value === value)?.labelKey ??
+        "settings.general.timeFormat.option.locale",
+    );
+
   return (
     
 
+            
+                      updateSettings({
+                        locale: defaults.locale,
+                      })
+                    }
+                  />
+                ) : null
+              }
+              control={
+                
+              }
+            />
+
              {
-                    if (value !== "locale" && value !== "12-hour" && value !== "24-hour") {
+                    if (!TIMESTAMP_FORMAT_OPTIONS.some((option) => option.value === value)) {
                       return;
                     }
                     updateSettings({
-                      timestampFormat: value,
+                      timestampFormat: value as (typeof TIMESTAMP_FORMAT_OPTIONS)[number]["value"],
                     });
                   }}
                 >
                   
-                    {TIMESTAMP_FORMAT_LABELS[settings.timestampFormat]}
+                    
+                      {timestampFormatOptionLabel(settings.timestampFormat)}
+                    
                   
                   
-                    
-                      {TIMESTAMP_FORMAT_LABELS.locale}
-                    
-                    
-                      {TIMESTAMP_FORMAT_LABELS["12-hour"]}
-                    
-                    
-                      {TIMESTAMP_FORMAT_LABELS["24-hour"]}
-                    
+                    {TIMESTAMP_FORMAT_OPTIONS.map((option) => (
+                      
+                        {timestampFormatOptionLabel(option.value)}
+                      
+                    ))}
                   
                 
               }
diff --git a/package.json b/package.json
index d34a8da54..655aa5a6e 100644
--- a/package.json
+++ b/package.json
@@ -35,13 +35,13 @@
     "build": "turbo run build",
     "build:marketing": "turbo run build --filter=@okcode/marketing",
     "build:desktop": "turbo run build --filter=@okcode/desktop --filter=okcodes",
-    "typecheck": "turbo run typecheck",
-    "lint": "oxlint --report-unused-disable-directives",
+    "typecheck": "bunx turbo run typecheck",
+    "lint": "bunx oxlint --report-unused-disable-directives",
     "test": "turbo run test",
     "test:desktop-smoke": "turbo run smoke-test --filter=@okcode/desktop",
     "test:desktop-release-smoke": "turbo run release-smoke --filter=@okcode/desktop",
-    "fmt": "oxfmt",
-    "fmt:check": "oxfmt --check",
+    "fmt": "bunx oxfmt",
+    "fmt:check": "bunx oxfmt --check",
     "build:contracts": "turbo run build --filter=@okcode/contracts",
     "dist:desktop:artifact": "node scripts/build-desktop-artifact.ts",
     "dist:desktop:dmg": "node scripts/build-desktop-artifact.ts --platform mac --target dmg",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 9d6bbfcf3..c05f516cf 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -20,7 +20,7 @@
     "dev": "tsdown src/index.ts --format esm,cjs --dts --watch --clean",
     "build": "tsdown src/index.ts --format esm,cjs --dts --clean",
     "prepare": "node -e \"process.exit(process.env.CI ? 0 : 1)\" || node ../../scripts/patch-effect-language-service.ts",
-    "typecheck": "tsc --noEmit",
+    "typecheck": "bunx tsc@5.7.3 --noEmit",
     "test": "vitest run"
   },
   "dependencies": {
diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts
index c765ffbb4..e7154983b 100644
--- a/packages/contracts/src/orchestration.ts
+++ b/packages/contracts/src/orchestration.ts
@@ -746,7 +746,7 @@ export type ProjectDeletedReason = typeof ProjectDeletedReason.Type;
 export const ProjectDeletedPayload = Schema.Struct({
   projectId: ProjectId,
   deletedAt: IsoDateTime,
-  reason: ProjectDeletedReason.pipe(Schema.withDecodingDefault(() => "manual")),
+  reason: ProjectDeletedReason.pipe(Schema.withDecodingDefault(() => "manual" as const)),
 });
 
 export const ThreadCreatedPayload = Schema.Struct({
@@ -771,7 +771,7 @@ export type ThreadDeletedReason = typeof ThreadDeletedReason.Type;
 export const ThreadDeletedPayload = Schema.Struct({
   threadId: ThreadId,
   deletedAt: IsoDateTime,
-  reason: ThreadDeletedReason.pipe(Schema.withDecodingDefault(() => "manual")),
+  reason: ThreadDeletedReason.pipe(Schema.withDecodingDefault(() => "manual" as const)),
 });
 
 export const ThreadMetaUpdatedPayload = Schema.Struct({
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 5951bd026..21aa7fe1b 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -63,7 +63,7 @@
   },
   "scripts": {
     "prepare": "node -e \"process.exit(process.env.CI ? 0 : 1)\" || node ../../scripts/patch-effect-language-service.ts",
-    "typecheck": "tsc --noEmit",
+    "typecheck": "bunx tsc@5.7.3 --noEmit",
     "test": "vitest run"
   },
   "dependencies": {

From 46a85185edf0347c19cdca7fa4019ee2f76db0e4 Mon Sep 17 00:00:00 2001
From: Val Alexander 
Date: Sun, 12 Apr 2026 21:56:26 -0500
Subject: [PATCH 2/2] Refine auth proxy and server event handling

- simplify request and header handling in the auth router
- make WebSocket HTTP routing await API handlers safely
- clean up gateway waiter iteration and scope normalization
---
 apps/server/src/api/authRouter.ts             |   6 +-
 apps/server/src/api/router.test.ts            | 135 +++++++++---------
 apps/server/src/openclaw/GatewayClient.ts     |   2 +-
 .../Layers/OpenclawGatewayConfig.ts           |   2 +-
 apps/server/src/sme/authValidation.ts         |   5 -
 apps/server/src/wsServer.ts                   |   4 +-
 6 files changed, 79 insertions(+), 75 deletions(-)

diff --git a/apps/server/src/api/authRouter.ts b/apps/server/src/api/authRouter.ts
index 0d04fc96e..c7f9cd29f 100644
--- a/apps/server/src/api/authRouter.ts
+++ b/apps/server/src/api/authRouter.ts
@@ -26,7 +26,7 @@ function respondJson(
 ): void {
   res.writeHead(statusCode, {
     "Content-Type": "application/json",
-    ...(headers ?? {}),
+    ...headers,
   });
   res.end(JSON.stringify(body));
 }
@@ -52,7 +52,9 @@ function mergeAnthropicBetaHeader(value: string | null): string {
   return parts.join(",");
 }
 
-async function readJsonRequestBody(req: http.IncomingMessage): Promise | null> {
+async function readJsonRequestBody(
+  req: http.IncomingMessage,
+): Promise | null> {
   const chunks: Buffer[] = [];
   for await (const chunk of req) {
     chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
diff --git a/apps/server/src/api/router.test.ts b/apps/server/src/api/router.test.ts
index 7c5fbb89b..c507bb8d6 100644
--- a/apps/server/src/api/router.test.ts
+++ b/apps/server/src/api/router.test.ts
@@ -87,21 +87,24 @@ describe("createApiRouter", () => {
       tokenManager,
     });
 
-    await withServer((req, res) => {
-      const url = new URL(req.url ?? "/", "http://127.0.0.1");
-      void tryHandleApiRequest(req, res, url);
-    }, async (baseUrl) => {
-      const response = await request(baseUrl, "/api/pairing?ttl=300");
-      const body = JSON.parse(response.body) as {
-        pairingUrl: string;
-        serverUrl: string;
-        expiresAt: string;
-      };
-      expect(response.statusCode).toBe(200);
-      expect(body.serverUrl).toBe("http://127.0.0.1:31337");
-      expect(body.pairingUrl).toContain("okcode://pair?server=");
-      expect(body.expiresAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
-    });
+    await withServer(
+      (req, res) => {
+        const url = new URL(req.url ?? "/", "http://127.0.0.1");
+        void tryHandleApiRequest(req, res, url);
+      },
+      async (baseUrl) => {
+        const response = await request(baseUrl, "/api/pairing?ttl=300");
+        const body = JSON.parse(response.body) as {
+          pairingUrl: string;
+          serverUrl: string;
+          expiresAt: string;
+        };
+        expect(response.statusCode).toBe(200);
+        expect(body.serverUrl).toBe("http://127.0.0.1:31337");
+        expect(body.pairingUrl).toContain("okcode://pair?server=");
+        expect(body.expiresAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
+      },
+    );
   });
 
   it("proxies Anthropic message requests with the Claude Code envelope", async () => {
@@ -142,49 +145,50 @@ describe("createApiRouter", () => {
       anthropicBaseUrl: `http://127.0.0.1:${upstreamAddress.port}`,
     });
 
-    await withServer((req, res) => {
-      const url = new URL(req.url ?? "/", "http://127.0.0.1");
-      void tryHandleApiRequest(req, res, url);
-    }, async (baseUrl) => {
-      const response = await request(baseUrl, "/api/auth/anthropic/v1/messages", {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-          "x-api-key": "test-key",
-          "anthropic-version": "2023-06-01",
-          "anthropic-beta": "tools-2024-04-04",
-        },
-        body: JSON.stringify({
+    await withServer(
+      (req, res) => {
+        const url = new URL(req.url ?? "/", "http://127.0.0.1");
+        void tryHandleApiRequest(req, res, url);
+      },
+      async (baseUrl) => {
+        const response = await request(baseUrl, "/api/auth/anthropic/v1/messages", {
+          method: "POST",
+          headers: {
+            "Content-Type": "application/json",
+            "x-api-key": "test-key",
+            "anthropic-version": "2023-06-01",
+            "anthropic-beta": "tools-2024-04-04",
+          },
+          body: JSON.stringify({
+            model: "claude-sonnet-4-20250514",
+            max_tokens: 64,
+            system: "Original system prompt",
+            messages: [{ role: "user", content: "Hello" }],
+            stream: true,
+          }),
+        });
+
+        expect(response.statusCode).toBe(200);
+        expect(JSON.parse(response.body)).toEqual({ ok: true, proxied: true });
+        expect(upstreamHeaders?.["x-api-key"]).toBe("test-key");
+        expect(upstreamHeaders?.["anthropic-version"]).toBe("2023-06-01");
+        expect(upstreamHeaders?.["anthropic-beta"]).toBe("claude-code-20250219,tools-2024-04-04");
+        expect(upstreamBody).toMatchObject({
           model: "claude-sonnet-4-20250514",
-          max_tokens: 64,
-          system: "Original system prompt",
-          messages: [{ role: "user", content: "Hello" }],
           stream: true,
-        }),
-      });
-
-      expect(response.statusCode).toBe(200);
-      expect(JSON.parse(response.body)).toEqual({ ok: true, proxied: true });
-      expect(upstreamHeaders?.["x-api-key"]).toBe("test-key");
-      expect(upstreamHeaders?.["anthropic-version"]).toBe("2023-06-01");
-      expect(upstreamHeaders?.["anthropic-beta"]).toBe(
-        "claude-code-20250219,tools-2024-04-04",
-      );
-      expect(upstreamBody).toMatchObject({
-        model: "claude-sonnet-4-20250514",
-        stream: true,
-      });
-      expect(upstreamBody?.system).toEqual([
-        {
-          type: "text",
-          text: "You are Claude Code, Anthropic's official CLI for Claude.",
-        },
-        {
-          type: "text",
-          text: "Original system prompt",
-        },
-      ]);
-    });
+        });
+        expect(upstreamBody?.system).toEqual([
+          {
+            type: "text",
+            text: "You are Claude Code, Anthropic's official CLI for Claude.",
+          },
+          {
+            type: "text",
+            text: "Original system prompt",
+          },
+        ]);
+      },
+    );
   });
 
   it("returns a JSON 404 for unknown API routes", async () => {
@@ -195,13 +199,16 @@ describe("createApiRouter", () => {
       tokenManager: new TokenManager(undefined),
     });
 
-    await withServer((req, res) => {
-      const url = new URL(req.url ?? "/", "http://127.0.0.1");
-      void tryHandleApiRequest(req, res, url);
-    }, async (baseUrl) => {
-      const response = await request(baseUrl, "/api/unknown");
-      expect(response.statusCode).toBe(404);
-      expect(JSON.parse(response.body)).toEqual({ error: "Not Found" });
-    });
+    await withServer(
+      (req, res) => {
+        const url = new URL(req.url ?? "/", "http://127.0.0.1");
+        void tryHandleApiRequest(req, res, url);
+      },
+      async (baseUrl) => {
+        const response = await request(baseUrl, "/api/unknown");
+        expect(response.statusCode).toBe(404);
+        expect(JSON.parse(response.body)).toEqual({ error: "Not Found" });
+      },
+    );
   });
 });
diff --git a/apps/server/src/openclaw/GatewayClient.ts b/apps/server/src/openclaw/GatewayClient.ts
index 734bbee23..3bff51c5f 100644
--- a/apps/server/src/openclaw/GatewayClient.ts
+++ b/apps/server/src/openclaw/GatewayClient.ts
@@ -466,7 +466,7 @@ export class OpenclawGatewayClient {
 
       if (frame.type === "event" && typeof frame.event === "string") {
         let matchedWaiter = false;
-        for (const waiter of [...this.pendingEventWaiters]) {
+        for (const waiter of this.pendingEventWaiters) {
           if (waiter.eventName === frame.event) {
             matchedWaiter = true;
             this.pendingEventWaiters.delete(waiter);
diff --git a/apps/server/src/persistence/Layers/OpenclawGatewayConfig.ts b/apps/server/src/persistence/Layers/OpenclawGatewayConfig.ts
index 26ec67d32..310e0939d 100644
--- a/apps/server/src/persistence/Layers/OpenclawGatewayConfig.ts
+++ b/apps/server/src/persistence/Layers/OpenclawGatewayConfig.ts
@@ -68,7 +68,7 @@ function normalizeScopes(scopes: ReadonlyArray | undefined): string[] {
       unique.add(trimmed);
     }
   }
-  return [...unique].sort((left, right) => left.localeCompare(right));
+  return [...unique].toSorted((left, right) => left.localeCompare(right));
 }
 
 function fromGeneratedIdentity(identity: ReturnType) {
diff --git a/apps/server/src/sme/authValidation.ts b/apps/server/src/sme/authValidation.ts
index 3dfc18f99..20cadfbe5 100644
--- a/apps/server/src/sme/authValidation.ts
+++ b/apps/server/src/sme/authValidation.ts
@@ -18,11 +18,6 @@ import {
 
 const OPENAI_MODEL_PROVIDERS = new Set(["openai"]);
 
-function normalizeOptionalValue(value: string | undefined | null): string | null {
-  const trimmed = value?.trim();
-  return trimmed && trimmed.length > 0 ? trimmed : null;
-}
-
 export function getAllowedSmeAuthMethods(provider: ProviderKind): readonly SmeAuthMethod[] {
   switch (provider) {
     case "claudeAgent":
diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts
index 8aa9df6f0..066c855b0 100644
--- a/apps/server/src/wsServer.ts
+++ b/apps/server/src/wsServer.ts
@@ -647,7 +647,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
   });
 
   // HTTP server — serves static files or redirects to Vite dev server
-  const httpServer = http.createServer((req, res) => {
+  const httpServer = http.createServer(async (req, res) => {
     const respond = (
       statusCode: number,
       headers: Record,
@@ -660,7 +660,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
     void Effect.runPromise(
       Effect.gen(function* () {
         const url = new URL(req.url ?? "/", `http://localhost:${port}`);
-        if (await tryHandleApiRequest(req, res, url)) {
+        if (yield* Effect.promise(() => tryHandleApiRequest(req, res, url))) {
           return;
         }