Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 4 additions & 2 deletions apps/server/src/api/authRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function respondJson(
): void {
res.writeHead(statusCode, {
"Content-Type": "application/json",
...(headers ?? {}),
...headers,
});
res.end(JSON.stringify(body));
}
Expand All @@ -52,7 +52,9 @@ function mergeAnthropicBetaHeader(value: string | null): string {
return parts.join(",");
}

async function readJsonRequestBody(req: http.IncomingMessage): Promise<Record<string, unknown> | null> {
async function readJsonRequestBody(
req: http.IncomingMessage,
): Promise<Record<string, unknown> | null> {
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
Expand Down
135 changes: 71 additions & 64 deletions apps/server/src/api/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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" });
},
);
});
});
2 changes: 1 addition & 1 deletion apps/server/src/openclaw/GatewayClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function normalizeScopes(scopes: ReadonlyArray<string> | 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<typeof generateOpenclawDeviceIdentity>) {
Expand Down
5 changes: 0 additions & 5 deletions apps/server/src/sme/authValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/wsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>,
Expand All @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/settings/SettingsRouteContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/components/settings/SettingsShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -142,7 +144,7 @@ export function SettingsShell({
disabled={changedSettingLabels.length === 0}
onClick={() => void onRestoreDefaults()}
>
Restore defaults
{t("common.actions.restoreDefaults")}
</Button>
</div>
</div>
Expand All @@ -163,7 +165,7 @@ export function SettingsShell({
disabled={changedSettingLabels.length === 0}
onClick={() => void onRestoreDefaults()}
>
Restore defaults
{t("common.actions.restoreDefaults")}
</Button>
</div>
</div>
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ type SidebarContextProps = {
toggleSidebar: () => void;
};

type CookieStoreLike = {
set(options: { expires: number; name: string; path: string; value: string }): Promise<void>;
};

type SidebarResizableOptions = {
maxWidth?: number;
minWidth?: number;
Expand Down Expand Up @@ -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: "/",
Expand Down
Loading
Loading