Restore desktop usage and subscription discovery across harnesses (#5844)

* fix(app): hide the sidebar Usage item while it has no summary

Without a summary window with data, the item fell back to a plain "Usage"
row that duplicated the footer's Usage icon. It now renders nothing, and the
footer rows leave it out so an otherwise empty rows block and its separator
disappear with it.

* fix(desktop): restore built-in usage in packaged apps

The external plugin compiler cannot traverse app.asar, and dependency packaging drops declarations needed by import validation. Ship complete built-in resources beside the archive and verify registration in the packaged desktop smoke check.

* fix(usage): restore login discovery across harnesses

Discover subscription accounts independently of agent sessions and retain each account’s logins in preference order so an unusable credential can fall back to another login for the same account.
This commit is contained in:
Mohamed Boudra
2026-10-01 14:51:11 +01:00
committed by GitHub
parent e10f6d2e2c
commit 70df09daeb
33 changed files with 1580 additions and 223 deletions
+43 -17
View File
@@ -1,10 +1,14 @@
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, expect, test } from "vitest";
import { fetchUsage, identify } from "./usage.js";
import { inputSchema } from "../shared/input.js";
import { inputSchema, type CodexUsageInput } from "../shared/input.js";
function authInput(directory: string): CodexUsageInput {
return { route: { store: "codex", path: join(directory, "auth.json") } };
}
let fixtureHome: string;
beforeEach(async () => {
@@ -42,7 +46,7 @@ function response(headers: HeadersInit, accountId?: string): Promise<Response> {
);
}
test("default input reads CODEX_HOME auth and preserves the usage request", async () => {
test("explicit route reads Codex auth and preserves the usage request", async () => {
const home = await mkdtemp(join(tmpdir(), "usage-codex-"));
try {
process.env["CODEX_HOME"] = home;
@@ -52,7 +56,7 @@ test("default input reads CODEX_HOME auth and preserves the usage request", asyn
tokens: { access_token: "fixture-default", account_id: "account-default" },
}),
);
const report = await fetchUsage({}, (_url, init) =>
const report = await fetchUsage(authInput(home), (_url, init) =>
response(init?.headers ?? {}, "account-default"),
);
expect(report).toMatchObject({
@@ -65,7 +69,7 @@ test("default input reads CODEX_HOME auth and preserves the usage request", asyn
}
});
test("discovery input rejects explicit credential routes", () => {
test("discovery input requires a route and rejects raw credentials", () => {
for (const input of [
{ codexHome: "/unused" },
{ accessToken: "unused" },
@@ -73,18 +77,20 @@ test("discovery input rejects explicit credential routes", () => {
]) {
expect(inputSchema.safeParse(input).success).toBe(false);
}
expect(inputSchema.parse({})).toEqual({});
expect(() => inputSchema.parse({})).toThrow();
});
test("identify returns a key when fetch finds Codex token credentials", async () => {
await writeAuth("fixture-supplied", "account-supplied");
await fetchUsage({}, (_url, init) => response(init?.headers ?? {}, "account-supplied"));
expect(await identify({})).toEqual({ key: "account-supplied" });
await fetchUsage(authInput(fixtureHome), (_url, init) =>
response(init?.headers ?? {}, "account-supplied"),
);
expect(await identify(authInput(fixtureHome))).toEqual({ key: "account-supplied" });
});
test("coerces credit balance and marks a 96 percent window dangerous", async () => {
const report = await fetchUsage(
{},
authInput(fixtureHome),
async () =>
new Response(
JSON.stringify({
@@ -110,7 +116,7 @@ test("coerces credit balance and marks a 96 percent window dangerous", async ()
test("summarizes the session and weekly windows by default, not code review", async () => {
const report = await fetchUsage(
{},
authInput(fixtureHome),
async () =>
new Response(
JSON.stringify({
@@ -132,7 +138,7 @@ test("summarizes the session and weekly windows by default, not code review", as
test("HTML usage body is unavailable", async () => {
const report = await fetchUsage(
{},
authInput(fixtureHome),
async () => new Response("<html>Login</html>", { status: 200 }),
);
expect(report.status).toBe("unavailable");
@@ -155,7 +161,7 @@ test("401 leaves auth.json byte for byte unchanged and makes no refresh request"
});
await writeFile(authPath, before);
let calls = 0;
const report = await fetchUsage({}, async (url) => {
const report = await fetchUsage(authInput(home), async (url) => {
expect(String(url)).toBe("https://chatgpt.com/backend-api/wham/usage");
calls++;
return new Response(null, { status: 401 });
@@ -168,15 +174,15 @@ test("401 leaves auth.json byte for byte unchanged and makes no refresh request"
}
});
test("default auth account claim survives token rotation", async () => {
test("auth account claim survives token rotation", async () => {
const token = (suffix: string) =>
`header.${Buffer.from(JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "work-id" }, suffix })).toString("base64url")}.signature`;
await writeAuth(token("first"));
expect(await identify({})).toEqual({ key: "work-id" });
expect(await identify(authInput(fixtureHome))).toEqual({ key: "work-id" });
await writeAuth(token("second"));
expect(await identify({})).toEqual({ key: "work-id" });
expect(await identify(authInput(fixtureHome))).toEqual({ key: "work-id" });
await writeAuth("opaque-token");
expect(await identify({})).toBeNull();
expect(await identify(authInput(fixtureHome))).toBeNull();
});
test("identify reads an email label from auth id_token when access token has no profile", async () => {
@@ -190,7 +196,7 @@ test("identify reads an email label from auth id_token when access token has no
tokens: { account_id: "account-id", access_token: "opaque-token", id_token: idToken },
}),
);
expect(await identify({})).toEqual({
expect(await identify(authInput(home))).toEqual({
key: "account-id",
label: "id-owner@example.test",
});
@@ -198,3 +204,23 @@ test("identify reads an email label from auth id_token when access token has no
await rm(home, { recursive: true, force: true });
}
});
test("discovers a Pi OAuth login independently of the CLI login", async () => {
const { discover } = await import("./usage.js");
const directory = await mkdtemp(join(tmpdir(), "usage-pi-discovery-"));
try {
await mkdir(join(directory, ".pi", "agent"), { recursive: true });
await writeFile(
join(directory, ".pi", "agent", "auth.json"),
JSON.stringify({
"openai-codex": { type: "oauth", access: "fixture-pi", accountId: "pi-account" },
}),
);
const inputs = await discover({ home: directory, env: {}, platform: "linux" });
expect(inputs).toContainEqual({
route: { store: "pi", path: join(directory, ".pi", "agent", "auth.json") },
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});