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
1 change: 1 addition & 0 deletions changelog.d/fixes/8233-codex-dashboard-regressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(dashboard):** Normalize Codex client URLs, restore the analytics input-bar color, resolve OAuth modal TypeScript regressions, synchronize the documented compression-engine count, and sanitize Muse Spark fetch errors ([#8233](https://github.com/diegosouzapw/OmniRoute/pull/8233)) — thanks @nguyenha935
8 changes: 4 additions & 4 deletions open-sse/executors/muse-spark-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import WebSocket from "ws";
import { BaseExecutor, mergeUpstreamExtraHeaders, type ExecuteInput } from "./base.ts";
import { getRotatingApiKey } from "../services/apiKeyRotator.ts";
import { prepareToolMessages, buildToolAwareResult } from "../translator/webTools.ts";
import { sanitizeErrorMessage } from "../utils/error.ts";
import {
normalizeSessionCookieHeader,
normalizeSessionCookieHeaders,
} from "@/lib/providers/webCookieAuth";
import { type ParsedMetaAiResponse, isRecord } from "./muse-spark-web/response-parser.ts";

const META_AI_GRAPHQL_API = "https://www.meta.ai/api/graphql";
// Meta rebranded the chat product from "Abra" to "Ecto"; the session cookie
// `abra_sess` was replaced by `ecto_1_sess`. `normalizeSessionCookieHeader`
// only uses this constant when the user pastes a bare cookie value with no
// Meta rebranded "Abra" to "Ecto"; `abra_sess` became `ecto_1_sess`.
// `normalizeSessionCookieHeader` only uses this constant for bare values with no
// `name=` prefix; full cookie lines (with any cookie names) pass through
// untouched, so users who paste their entire DevTools cookie line still work.
const META_AI_DEFAULT_COOKIE = "ecto_1_sess";
Expand Down Expand Up @@ -522,7 +522,7 @@ function buildErrorResponse(status: number, message: string, code?: string | nul
return new Response(
JSON.stringify({
error: {
message,
message: sanitizeErrorMessage(message),
type: "upstream_error",
...(code ? { code } : {}),
},
Expand Down
4 changes: 2 additions & 2 deletions src/shared/components/OAuthModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export default function OAuthModal({
let token: string | Record<string, unknown> = raw;
if (provider === "grok-cli") {
const parsed = parseGrokCliPasteToken(raw);
if (!parsed.ok) {
if (parsed.ok === false) {
setError(parsed.error);
return;
}
Expand Down Expand Up @@ -1084,7 +1084,7 @@ export default function OAuthModal({
<LinkifiedText text={error} />
</p>
<div className="flex gap-2">
<Button onClick={startOAuthFlow} variant="secondary" fullWidth>
<Button onClick={() => startOAuthFlow()} variant="secondary" fullWidth>
{t("tryAgain")}
</Button>
<Button onClick={handleClose} variant="ghost" fullWidth>
Expand Down
2 changes: 1 addition & 1 deletion src/shared/components/analytics/rechartsUsageCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function DailyTrendChartBody({ chartData, hasCost }) {
<Bar
dataKey={t("chartInput")}
stackId="a"
fill="var(--primary)"
fill="var(--color-primary)"
opacity={0.7}
radius={[0, 0, 0, 0]}
animationDuration={600}
Expand Down
6 changes: 3 additions & 3 deletions src/shared/utils/codexBaseUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ function normalizeUrlPath(pathname: string, wireApi: CodexWireApi): string {
}

path = path.replace(/\/v1$/i, "").replace(/\/api$/i, "");
const apiPath = `${path}/api/v1`.replace(/\/{2,}/g, "/");
return apiPath.startsWith("/") ? apiPath : `/${apiPath}`;
const v1Path = `${path}/v1`.replace(/\/{2,}/g, "/");
return v1Path.startsWith("/") ? v1Path : `/${v1Path}`;
}

export function normalizeCodexBaseUrl(baseUrl: string, wireApi: CodexWireApi = "chat"): string {
Expand All @@ -27,6 +27,6 @@ export function normalizeCodexBaseUrl(baseUrl: string, wireApi: CodexWireApi = "
if (wireApi === "responses") {
normalized = normalized.replace(/\/responses(?:\/.*)?$/i, "");
}
return normalized.replace(/\/v1$/i, "").replace(/\/api$/i, "") + "/api/v1";
return normalized.replace(/\/v1$/i, "").replace(/\/api$/i, "") + "/v1";
}
}
8 changes: 4 additions & 4 deletions tests/unit/check-docs-counts-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ test("free-tier gate passes when a file carries no headline at all", () => {

// --- Generic numeric-claim gate (engines / MCP tools / scopes / CLI) --------
// Extends the same drift guard to the counts that silently drifted in v3.8.49:
// 10→11 engines, 94→104 MCP tools, 30→31 scopes, 26→33 CLI tools.
// 11→12 engines, 94→104 MCP tools, 30→31 scopes, 26→33 CLI tools.
import { makeNumberClaimValidator } from "../../scripts/check/check-docs-counts-sync.mjs";

const makeValidator = makeNumberClaimValidator as (
Expand Down Expand Up @@ -182,12 +182,12 @@ test("MCP-tools gate ignores per-module counts and the CLI catalog total", () =>
});

test("compression-engines and CLI-tools gates catch their v3.8.49 drift", () => {
const eng = makeValidator(11, {
const eng = makeValidator(12, {
what: "compression engines",
pattern: /(\d+)[-\s](?:engine stack|composable engines|stacked engines)/gi,
});
assert.equal(eng("11-engine stack").ok, true);
assert.equal(eng("10-engine stack").ok, false);
assert.equal(eng("12-engine stack").ok, true);
assert.equal(eng("11-engine stack").ok, false);

const cli = makeValidator(33, {
what: "CLI tools",
Expand Down
34 changes: 23 additions & 11 deletions tests/unit/codex-base-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,48 @@ import assert from "node:assert/strict";

import { normalizeCodexBaseUrl } from "../../src/shared/utils/codexBaseUrl.ts";

test("normalizeCodexBaseUrl keeps Codex responses base URLs at the API root", () => {
test("normalizeCodexBaseUrl keeps Codex responses base URLs at the canonical v1 root", () => {
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/v1/responses", "responses"),
"http://127.0.0.1:20128/api/v1"
"http://127.0.0.1:20128/v1"
);
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/api/v1/responses", "responses"),
"http://127.0.0.1:20128/api/v1"
"http://127.0.0.1:20128/v1"
);
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/v1", "responses"),
"http://127.0.0.1:20128/api/v1"
"http://127.0.0.1:20128/v1"
);
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/v1/responses/responses", "responses"),
"http://127.0.0.1:20128/api/v1"
"http://127.0.0.1:20128/v1"
);
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/v1/responses/compact", "responses"),
"http://127.0.0.1:20128/api/v1"
"http://127.0.0.1:20128/v1"
);
});

test("normalizeCodexBaseUrl leaves chat normalization behavior intact", () => {
test("normalizeCodexBaseUrl uses the bridge-compatible v1 path for chat", () => {
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128/v1", "chat"),
"http://127.0.0.1:20128/api/v1"
normalizeCodexBaseUrl("http://127.0.0.1:20131/v1", "chat"),
"http://127.0.0.1:20131/v1"
);
assert.equal(
normalizeCodexBaseUrl("http://127.0.0.1:20128", "chat"),
"http://127.0.0.1:20128/api/v1"
normalizeCodexBaseUrl("http://127.0.0.1:20131", "chat"),
"http://127.0.0.1:20131/v1"
);
assert.equal(
normalizeCodexBaseUrl("https://api.example.com/api/v1", "chat"),
"https://api.example.com/v1"
);
assert.equal(
normalizeCodexBaseUrl("https://api.example.com/proxy/api/v1?source=ui#config", "chat"),
"https://api.example.com/proxy/v1"
);
});

test("normalizeCodexBaseUrl keeps relative fallback URLs on v1", () => {
assert.equal(normalizeCodexBaseUrl("/proxy/api/v1", "chat"), "/proxy/v1");
});
19 changes: 19 additions & 0 deletions tests/unit/muse-spark-web-continuation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,22 @@ test("muse-spark-web: GraphQL error in 200 response is detected", async () => {
restore();
}
});

test("muse-spark-web: fetch failures do not expose stack traces or source paths", async () => {
__resetMuseSparkConversationCacheForTesting();
const executor = new MuseSparkWebExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error("socket failed at /srv/omniroute/secrets.ts:42\n at fetchGraphql");
};

try {
const result = await executor.execute(withConnection("conn-fetch-error"));
assert.equal(result.response.status, 502);
const body = await result.response.json();
assert.equal(body.error.message, "Warmup fetch failed: socket failed at <path>");
assert.doesNotMatch(body.error.message, /secrets\.ts|fetchGraphql|\n/);
} finally {
globalThis.fetch = originalFetch;
}
});
9 changes: 5 additions & 4 deletions tests/unit/oauth-modal-grok-cli-paste-7610.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import { dirname, join } from "node:path";
// The modal must refuse bare JWT pastes and require full auth.json with refresh_token.

const here = dirname(fileURLToPath(import.meta.url));
const modalSource = readFileSync(
join(here, "../../src/shared/components/OAuthModal.tsx"),
const modalSource = readFileSync(join(here, "../../src/shared/components/OAuthModal.tsx"), "utf8");
const parserSource = readFileSync(
join(here, "../../src/lib/oauth/utils/grokCliAuthJson.ts"),
"utf8"
);

test("#7610: OAuthModal rejects bare Grok JWT paste instructions", () => {
assert.match(modalSource, /parseGrokCliPasteToken/);
assert.match(modalSource, /Do not paste only the JWT/);
assert.match(modalSource, /full ~\/\.grok\/auth\.json/);
assert.match(parserSource, /Do not paste only the JWT/);
assert.match(parserSource, /full ~\/\.grok\/auth\.json/);
assert.doesNotMatch(
modalSource,
/Paste your Grok Build JWT token from ~\/\.grok\/auth\.json \(the "key" field value\)/
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/oauth-modal-typescript-regressions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";

const modalSource = readFileSync("src/shared/components/OAuthModal.tsx", "utf8");

test("OAuthModal narrows failed Grok paste results before reading the error", () => {
assert.match(modalSource, /if \(parsed\.ok === false\) \{\s*setError\(parsed\.error\);/);
});

test("OAuthModal adapts the retry action to the button click handler", () => {
assert.match(
modalSource,
/<Button onClick=\{\(\) => startOAuthFlow\(\)\} variant="secondary" fullWidth>/
);
});
10 changes: 10 additions & 0 deletions tests/unit/shared/analytics-recharts-lazy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ test("usage analytics keeps Recharts behind the lazy loader boundary", () => {
assert.doesNotMatch(source, /import\("recharts"\)/);
}
});

test("usage analytics bars use the defined primary color token", () => {
const chartSource = readFileSync(
"src/shared/components/analytics/rechartsUsageCharts.tsx",
"utf8"
);

assert.match(chartSource, /fill="var\(--color-primary\)"/);
assert.doesNotMatch(chartSource, /fill="var\(--primary\)"/);
});
Loading