Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ba79610
fix(web,mobile): say that removing a T3 Connect environment keeps its…
juliusmarminge Sep 28, 2026
72330e2
feat(models): add Claude Sonnet 5.5 (#14152)
arturict Sep 28, 2026
b528a70
fix(server): restore Windows terminal startup after node-pty upgrade …
SegFaultZero Sep 28, 2026
7733bc8
fix(desktop): make Linux URL handlers discoverable with the app icon …
limwa Sep 28, 2026
b21f3b7
fix(server): prevent Windows PTY helper crashes in watch mode (#14179)
UtkarshUsername Sep 28, 2026
38f3c62
fix(shared): local-path remotes no longer crash legacy PR link projec…
pujitha24 Sep 29, 2026
adfc924
fix(server): preserve usage in oversized transcript records (#13650)
shivamhwp Sep 29, 2026
e518866
fix(server): OpenCode stop no longer hangs when a turn is interrupted…
SunkenInTime Sep 29, 2026
5da5595
feat: save Bitbucket credentials from Source Control settings (#14103)
gabrielelpidio Sep 29, 2026
76fa23d
fix(provider): mark OpenCode v2 incompatible (#14198)
juliusmarminge Sep 29, 2026
d2c9281
fix(web): pull request badge sits on the sidebar row's baseline (#14007)
Jardo-51 Sep 29, 2026
27bdf1a
feat(codex): connect ChatGPT accounts with managed authentication (#1…
juliusmarminge Sep 29, 2026
5e83e99
Update currentModels in model-manifest.json
juliusmarminge Sep 29, 2026
2cbc24f
chore(release): prepare v0.0.43
t3-code[bot] Sep 29, 2026
451afcb
fix(codex): Pro Max accounts load, so Ultrafast shows up (#14304)
t3dotgg Sep 29, 2026
1a553d0
fix(shared): merge OpenCode Go limits by credential (#14209)
Yash-Singh1 Sep 29, 2026
4222485
feat(codex): regenerate protocol bindings for Codex 0.159 (#14311)
juliusmarminge Sep 29, 2026
ff1db03
chore(release): prepare v0.0.44
t3-code[bot] Sep 29, 2026
8792f95
test(server): stop pinning codex install advisory to a release range …
maria-rcks Sep 29, 2026
63b61e6
chore: stop CodeRabbit from editing PR descriptions (#14307)
t3dotgg Sep 30, 2026
60cb7d1
fix(web): unresolved pull request links use the compact link tooltip …
flamboh Sep 30, 2026
88fbc2c
fix(client-runtime): keep model ids in inline code from becoming file…
otavio Sep 30, 2026
916ec94
fix(web): show the agent's question on user-input timeline rows (#12900)
saphid Sep 30, 2026
55ec55b
fix(web): open workspace root links in the file explorer (#12449)
saphid Sep 30, 2026
050cfad
fix(grok): recover from crashed provider sessions (#10607)
saphid Sep 30, 2026
2a23c30
fix(web): let command menu descriptions use the full row width (#8865)
jakaskerjanc Sep 30, 2026
0fcd5f9
fix(web): multi-PR badges open the linked pull requests panel (#13211)
flamboh Sep 30, 2026
c18e5ea
fix(web): stop clipping the bottoms of diff file names (#14375)
shivamhwp Sep 30, 2026
54bf14a
Merge upstream/main through c18e5ea6ed
andrewcai8 Sep 30, 2026
da25b30
fix(server): cloud machines refuse managed ChatGPT Codex accounts
andrewcai8 Sep 30, 2026
9f0acb6
fix(shared): the fork's account matching also keys by credential
andrewcai8 Sep 30, 2026
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 .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
reviews:
high_level_summary: false
review_status: false
auto_review:
enabled: true
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.42",
"version": "0.0.44",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
112 changes: 112 additions & 0 deletions apps/desktop/src/app/CodexAuthCallback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// @effect-diagnostics nodeBuiltinImport:off globalFetch:off - Tests exercise the real native loopback listener without an OpenAI account.
import * as NodeHttp from "node:http";
import { describe, expect, it } from "vite-plus/test";
import { codexAuthDeliveryUrl, readCodexAuthDelivery } from "@t3tools/shared/codexAuthHandoff";
import { EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
import { receiveCodexAuthCallback, cancelCodexAuthCallback } from "./CodexAuthCallback.ts";

async function freePort() {
const server = NodeHttp.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
if (!address || typeof address === "string") throw new Error("address");
await new Promise<void>((resolve) => server.close(() => resolve()));
return address.port;
}
function request(port: number, state = "a".repeat(43)) {
const url = new URL("https://auth.openai.com/api/accounts/authorize");
url.search = new URLSearchParams({
client_id: "dynamic_agent_client",
response_type: "code",
redirect_uri: `http://127.0.0.1:${port}/auth/callback`,
state,
code_challenge_method: "S256",
code_challenge: "b".repeat(43),
}).toString();
return url.toString();
}
function callback(authorizationUrl: string) {
const request = new URL(authorizationUrl);
const url = new URL(request.searchParams.get("redirect_uri")!);
url.search = new URLSearchParams({
state: request.searchParams.get("state")!,
code: "test-code",
client_id: "oaiapp_test",
}).toString();
return url.toString();
}

describe("desktop Codex callback helper", () => {
it("binds before opening sign-in, ignores a foreign response, and returns only the code callback", async () => {
const authorizationUrl = request(await freePort());
const expected = callback(authorizationUrl);
const received = await receiveCodexAuthCallback(authorizationUrl, async () => {
const invalid = new URL(expected);
invalid.searchParams.set("state", "foreign");
expect((await fetch(invalid)).status).toBe(400);
const response = await fetch(expected);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(await response.text()).not.toContain("test-code");
return true;
});
expect(received).toBe(expected);
});
it("returns hosted web to the exact instance and environment without putting the code in its query", async () => {
const authorizationUrl = request(await freePort());
const expected = callback(authorizationUrl);
const input = {
authorizationUrl,
returnUrl: "https://app.t3.codes/settings/providers?environmentId=remote-one&instanceId=work",
environmentId: EnvironmentId.make("remote-one"),
instanceId: ProviderInstanceId.make("work"),
flowId: "flow-one",
};
await receiveCodexAuthCallback(
authorizationUrl,
async () => {
const response = await fetch(expected, { redirect: "manual" });
expect(response.status).toBe(303);
const delivery = response.headers.get("location")!;
expect(new URL(delivery).searchParams.has("code")).toBe(false);
expect(readCodexAuthDelivery(delivery)?.callbackUrl).toBe(expected);
expect(readCodexAuthDelivery(delivery)?.returnUrl).toBe(input.returnUrl);
return true;
},
(url) => codexAuthDeliveryUrl(input, url),
);
});
it("cancels and releases its listener so exact-port reauthorization can run again", async () => {
const authorizationUrl = request(await freePort());
await expect(
receiveCodexAuthCallback(authorizationUrl, async () => {
cancelCodexAuthCallback(authorizationUrl);
return true;
}),
).rejects.toThrow("cancelled");
expect(
await receiveCodexAuthCallback(authorizationUrl, async () => {
await fetch(callback(authorizationUrl));
return true;
}),
).toBe(callback(authorizationUrl));
});
it("allows two accounts to complete independently", async () => {
const a = request(await freePort(), "a".repeat(43));
const b = request(await freePort(), "c".repeat(43));
const openedA = Promise.withResolvers<void>();
const openedB = Promise.withResolvers<void>();
const receiveA = receiveCodexAuthCallback(a, async () => {
openedA.resolve();
await openedB.promise;
await fetch(callback(a));
return true;
});
const receiveB = receiveCodexAuthCallback(b, async () => {
openedB.resolve();
await openedA.promise;
await fetch(callback(b));
return true;
});
expect(await Promise.all([receiveA, receiveB])).toEqual([callback(a), callback(b)]);
});
});
5 changes: 5 additions & 0 deletions apps/desktop/src/app/CodexAuthCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
CodexAuthCallbackError,
cancelCodexAuthCallback,
receiveCodexAuthCallback,
} from "@t3tools/shared/codexAuthCallback";
148 changes: 146 additions & 2 deletions apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// @effect-diagnostics nodeBuiltinImport:off globalFetchInEffect:off - Hosted handoff test uses a real localhost listener without an OpenAI account.
import * as NodeHttp from "node:http";
import { codexAuthHandoffUrl, readCodexAuthDelivery } from "@t3tools/shared/codexAuthHandoff";
import { EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
import { HostProcessArguments } from "@t3tools/shared/hostProcess";
import { assert, describe, it } from "@effect/vitest";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
Expand All @@ -22,14 +27,24 @@ vi.mock("@clerk/electron/storage", () => ({
storage: storageMock,
}));

import * as Option from "effect/Option";
import * as Exit from "effect/Exit";
import * as FileSystem from "effect/FileSystem";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronShell from "../electron/ElectronShell.ts";
import * as ElectronWindow from "../electron/ElectronWindow.ts";
import * as DesktopClerk from "./DesktopClerk.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

const makeDesktopClerkLayer = (isDevelopment = true, events: string[] = []) => {
const makeDesktopClerkLayer = (
isDevelopment = true,
events: string[] = [],
shell: ElectronShell.ElectronShell["Service"] = {
openExternal: () => Effect.succeed(true),
openSystemSettings: () => Effect.succeed(false),
copyText: () => Effect.void,
},
) => {
const environment = DesktopEnvironment.DesktopEnvironment.of({
stateDir: "/tmp/t3-state",
isDevelopment,
Expand All @@ -51,6 +66,7 @@ const makeDesktopClerkLayer = (isDevelopment = true, events: string[] = []) => {
Layer.mergeAll(
Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment),
Layer.succeed(ElectronApp.ElectronApp, electronApp),
Layer.succeed(ElectronShell.ElectronShell, shell),
FileSystem.layerNoop({ exists: () => Effect.succeed(false) }),
),
),
Expand Down Expand Up @@ -162,7 +178,7 @@ describe("DesktopClerk", () => {

assert.isTrue(Exit.isSuccess(exit));
assert.equal(quit.mock.calls.length, 0);
assert.deepEqual(registeredEvents, ["second-instance"]);
assert.deepEqual(registeredEvents, ["open-url", "second-instance"]);
}).pipe(
Effect.provide(makeDesktopClerkLayer()),
Effect.provideService(ElectronApp.ElectronApp, electronApp),
Expand Down Expand Up @@ -198,3 +214,131 @@ describe("DesktopClerk", () => {
);
});
});

it.effect(
"provider auth deep links navigate and reveal the running desktop without handling Clerk URLs",
() => {
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: true });
const listeners = new Map<string, (...args: unknown[]) => void>();
const revealed = Promise.withResolvers<void>();
const loadURL = vi.fn(async (_url: string) => undefined);
const window = { loadURL };
const electronApp = {
on: (name: string, listener: (...args: unknown[]) => void) =>
Effect.sync(() => {
listeners.set(name, listener);
}),
} as unknown as ElectronApp.ElectronApp["Service"];
const electronWindow = {
currentMainOrFirst: Effect.succeed(Option.some(window)),
reveal: () => Effect.sync(() => revealed.resolve()),
} as unknown as ElectronWindow.ElectronWindow["Service"];
return Effect.gen(function* () {
const clerk = yield* DesktopClerk.DesktopClerk;
yield* clerk.configure;
const event = { preventDefault: vi.fn() };
listeners.get("open-url")!(event, "t3code-dev://app/auth/callback?code=clerk-code");
listeners.get("open-url")!(event, "t3code://app/welcome");
assert.equal(loadURL.mock.calls.length, 0);
assert.equal(event.preventDefault.mock.calls.length, 0);
listeners.get("second-instance")!({}, [
"t3",
"t3code-dev://app/settings/providers?instanceId=work&code=never-forward",
]);
yield* Effect.promise(() => revealed.promise);
assert.deepEqual(loadURL.mock.calls, [
["t3code-dev://app/settings/providers?instanceId=work"],
]);
listeners.get("open-url")!(event, "t3code-dev://app/welcome#agents:machine-id");
assert.equal(event.preventDefault.mock.calls.length, 1);
}).pipe(
Effect.scoped,
Effect.provide(makeDesktopClerkLayer()),
Effect.provideService(ElectronApp.ElectronApp, electronApp),
Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
);
},
);

for (const entry of ["startup", "open-url"] as const) {
it.effect(`receives hosted web sign-in through the desktop ${entry} handler`, () =>
Effect.gen(function* () {
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: true });
const port = yield* Effect.promise(async () => {
const server = NodeHttp.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
if (!address || typeof address === "string") throw new Error("address");
await new Promise<void>((resolve) => server.close(() => resolve()));
return address.port;
});
const authorize = new URL("https://auth.openai.com/api/accounts/authorize");
authorize.search = new URLSearchParams({
client_id: "dynamic_agent_client",
response_type: "code",
redirect_uri: `http://127.0.0.1:${port}/auth/callback`,
state: "a".repeat(43),
code_challenge_method: "S256",
code_challenge: "b".repeat(43),
}).toString();
const request = {
authorizationUrl: authorize.toString(),
returnUrl: "https://app.t3.codes/welcome#agents:remote-one",
environmentId: EnvironmentId.make("remote-one"),
instanceId: ProviderInstanceId.make("work"),
flowId: "flow-one",
};
const link = codexAuthHandoffUrl(request, true);
const delivered = Promise.withResolvers<string>();
const shell = ElectronShell.ElectronShell.of({
openExternal: (value) =>
Effect.promise(async () => {
const url = new URL(String(value));
const callback = new URL(url.searchParams.get("redirect_uri")!);
callback.search = new URLSearchParams({
state: url.searchParams.get("state")!,
code: "test-code",
client_id: "oaiapp_test",
}).toString();
const response = await fetch(callback, { redirect: "manual" });
delivered.resolve(response.headers.get("location")!);
return true;
}),
openSystemSettings: () => Effect.succeed(false),
copyText: () => Effect.void,
});
const listeners = new Map<string, (...args: unknown[]) => void>();
const electronApp = {
whenReady: Effect.void,
on: (name: string, listener: (...args: unknown[]) => void) =>
Effect.sync(() => {
listeners.set(name, listener);
}),
} as unknown as ElectronApp.ElectronApp["Service"];
yield* Effect.gen(function* () {
const clerk = yield* DesktopClerk.DesktopClerk;
yield* clerk.configure;
if (entry === "open-url") {
const event = { preventDefault: vi.fn() };
listeners.get("open-url")!(event, link);
assert.strictEqual(event.preventDefault.mock.calls.length, 1);
}
const delivery = readCodexAuthDelivery(yield* Effect.promise(() => delivered.promise));
assert.strictEqual(delivery?.environmentId, request.environmentId);
assert.strictEqual(delivery?.instanceId, request.instanceId);
assert.strictEqual(delivery?.flowId, request.flowId);
assert.strictEqual(delivery?.returnUrl, request.returnUrl);
}).pipe(
Effect.provide(makeDesktopClerkLayer(true, [], shell)),
Effect.provideService(HostProcessArguments, entry === "startup" ? ["t3", link] : ["t3"]),
Effect.provideService(ElectronApp.ElectronApp, electronApp),
Effect.provideService(
ElectronWindow.ElectronWindow,
{} as ElectronWindow.ElectronWindow["Service"],
),
);
}).pipe(Effect.scoped),
);
}
Loading
Loading