Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e759847
fix(acp): keep one answer when a running tool reports progress (#13386)
juliusmarminge Sep 24, 2026
66129c6
feat(web): run shell commands from chat in the thread terminal (#13060)
Bil0000 Sep 24, 2026
f3cb2a1
fix(antigravity): keep Windows runtime unpacking under MAX_PATH (#13389)
juliusmarminge Sep 24, 2026
567783e
fix(codex): the protocol generator runs again on Effect rc.115 (#13480)
juliusmarminge Sep 24, 2026
d5d4874
feat(codex): require Codex 0.156 and regenerate its protocol (#13481)
juliusmarminge Sep 24, 2026
0109670
feat(threads): add per-thread auto-settle switch (#11846)
t3dotgg Sep 24, 2026
8251c8d
fix(web): working and monitoring threads fade in the sidebar again (#…
t3dotgg Sep 24, 2026
a36af06
fix(server): streamed section titles wait for the text under them (#1…
t3dotgg Sep 24, 2026
720490a
fix(web): normalize disabled control opacity (#11441)
t3-code[bot] Sep 24, 2026
9a91177
fix(web): sidebar Back always returns to the main app (#13516)
t3dotgg Sep 24, 2026
d4a3345
fix(desktop): desktop updates reconnect in seconds, not minutes (#12006)
t3dotgg Sep 24, 2026
8d7b5e9
fix(connect): remove tunnels after hosts go offline (#9386)
t3dotgg Sep 25, 2026
9c524d5
fix(mobile): capture a lit 6.9-inch lock screen in the agent-activity…
juliusmarminge Sep 25, 2026
72447f2
feat(grok): offer one-click updates through `grok update` (#13523)
juliusmarminge Sep 25, 2026
59abcd6
fix(mobile): make Android subscription usage widgets scrollable (#13474)
tris203 Sep 25, 2026
fd46510
fix(web): keep sidebar terminal pulses in sync (#12962)
t3dotgg Sep 25, 2026
46f3c2c
feat(web): add iPhone Duo 3D controls (#12813)
juliusmarminge Sep 25, 2026
d23eab1
fix(relay): export tunnel cleanup counters to Axiom (#13528)
juliusmarminge Sep 25, 2026
0cab7d5
fix(server): Grok accounts with no usage yet no longer vanish from Li…
jakeleventhal Sep 25, 2026
91e53e5
fix(server): report the Grok account email so usage limits merge acro…
jakeleventhal Sep 25, 2026
3e2370f
feat(web): add usage page keybinding (#9434)
jakeleventhal Sep 25, 2026
568c9bc
chore: clear Effect language service suggestions (#13536)
juliusmarminge Sep 25, 2026
fc46b8c
ci(relay): add a forced manual relay deploy (#13550)
juliusmarminge Sep 25, 2026
ebdcda1
fix(web): selected text stays visible on a revealed file line (#13548)
t3dotgg Sep 25, 2026
29abbf9
fix(web): collapsed composer bar stops flipping its labels while you …
t3dotgg Sep 25, 2026
ab70c89
fix(clients): sync status no longer flickers when opening running thr…
t3dotgg Sep 25, 2026
6391be2
fix(server): newer Codex models get T3 Code's instructions again (#13…
t3dotgg Sep 25, 2026
9957349
feat(web): control Android foldables in the Device panel (#13534)
juliusmarminge Sep 25, 2026
4f27a84
fix(mcp): preview snapshots fit in the agent's tool output again (#13…
t3dotgg Sep 25, 2026
0c84b42
fix(web): paste after clicking away from the composer lands in it aga…
t3dotgg Sep 25, 2026
99641fd
feat(desktop): keep running threads synced in the background (#13554)
t3dotgg Sep 25, 2026
1e192b2
fix(mcp): preview errors tell agents what to do instead (#13559)
t3dotgg Sep 25, 2026
df9826f
feat(web): agents working banner links to the Agents panel (#13572)
t3dotgg Sep 25, 2026
7b84431
fix(web): size the Android fold model from the inner display (#13574)
juliusmarminge Sep 25, 2026
a107f8a
fix(clients): a preview app no longer knocks the desktop's own server…
t3dotgg Sep 25, 2026
20f0ff1
fix(web): keep nested task states out of parent bullets (#11477)
dominic-r Sep 25, 2026
c13f7d9
feat(release): ship a Linux .deb that updates itself (#13575)
t3dotgg Sep 25, 2026
d10bd13
perf(desktop): cache compiled JavaScript between launches (#13501)
t3dotgg Sep 25, 2026
b3de243
fix(dev): one t3.json setup action that works on every OS (#13589)
t3dotgg Sep 25, 2026
13d6b30
fix(web): new worktree threads no longer say "checkout" during setup …
t3dotgg Sep 25, 2026
86054b6
fix(desktop): `t3 app` keeps working after a second desktop app quits…
t3dotgg Sep 25, 2026
e3e7cc3
fix(usage): price Claude fast-mode requests at the fast rate (#13599)
t3dotgg Sep 25, 2026
e55e731
fix: update OpenAI logo to current brand asset (#13611)
aaditagrawal Sep 25, 2026
1c12706
fix(mobile): render assigned project icons in chat list (#12810)
SunkenInTime Sep 25, 2026
e5a46d6
feat(usage): read cursor, opencode, and antigravity history (#10409)
maria-rcks Sep 25, 2026
d06f0ff
fix(sqlite): retry failed statement preparations (#10584)
yashranaway Sep 25, 2026
7a12aff
fix(mobile): scale Android controls with appearance text size (#13356)
none23 Sep 25, 2026
a4f6078
fix(web): return focus to the composer after saving a citation note (…
mackinleysmith Sep 25, 2026
fd996d1
feat(observability): honor the standard OTLP endpoint, headers, and p…
yordis Sep 25, 2026
1a0c915
fix(terminal): settling a thread closes its idle shells (#13673)
t3dotgg Sep 25, 2026
f5bd2fd
fix(server): load Cursor keyring with createRequire (#13678)
Yash-Singh1 Sep 25, 2026
4293433
perf(server): avoid rereading unchanged files in review previews (#13…
shivamhwp Sep 25, 2026
2598ce7
feat(desktop): honor the standard OTLP variables in the main process …
yordis Sep 25, 2026
ed809f7
fix(web): restore compact provider instance badges (#13700)
t3-code[bot] Sep 25, 2026
525af2d
perf(mobile): lighter Home rows away from the viewport (#13702)
AKolenda Sep 26, 2026
aade3a6
fix(usage): price Cursor cache savings by base model (#13731)
Yash-Singh1 Sep 26, 2026
595a1e1
perf(mobile): render Home rows further ahead while scrolling (#13705)
AKolenda Sep 26, 2026
2a9832b
refactor(observability): name each service after its application (#13…
yordis Sep 26, 2026
a7473cf
chore: sync with upstream pingdotgg/t3code main
yordis Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 13 additions & 1 deletion .github/workflows/deploy-relay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ on:
push:
branches:
- main
# Alchemy does not redeploy the Worker when only a Config value read in its
# Init changes (alchemy-run/alchemy#1831), so a changed repository variable
# needs a forced deploy to reach production.
workflow_dispatch:
inputs:
force:
description: Redeploy every resource, including ones with no detected changes
type: boolean
default: true

permissions:
contents: read
Expand All @@ -17,6 +26,8 @@ concurrency:
jobs:
deploy_relay:
name: Deploy production relay
# A manual run from another branch would deploy that branch to production.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 15
environment:
Expand All @@ -28,6 +39,7 @@ jobs:
RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }}
RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }}
RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }}
RELAY_TUNNEL_CLEANUP_MODE: ${{ vars.RELAY_TUNNEL_CLEANUP_MODE }}
CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }}
CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }}
APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }}
Expand Down Expand Up @@ -70,7 +82,7 @@ jobs:

- name: Deploy production relay stage
if: steps.creds.outputs.configured == 'true'
run: vp run --filter t3code-relay deploy --stage prod --yes --no-input
run: vp run --filter t3code-relay deploy --stage prod --yes --no-input ${{ inputs.force && '--force' || '' }}
env:
# The PublishClientConfig action writes the client env here instead
# of the repo-root .env; nothing on the runner reads it.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ jobs:
"release/*.dmg"
"release/*.zip"
"release/*.AppImage"
"release/*.deb"
"release/*.exe"
)
# Preview builds have no publish config, so electron-builder writes
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ jobs:
clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }}
relay_url: ${{ needs.relay_public_config.outputs.relay_url }}
label: Linux arm64
runner: ubuntu-24.04-arm
runner: blacksmith-16vcpu-ubuntu-2404-arm
platform: linux
target: AppImage
arch: arm64
Expand Down Expand Up @@ -890,6 +890,7 @@ jobs:
echo 'release-assets/*.dmg'
echo 'release-assets/*.zip'
echo 'release-assets/*.AppImage'
echo 'release-assets/*.deb'
echo 'release-assets/*.exe'
if [[ "${{ needs.preflight.outputs.release_channel }}" != "preview" ]]; then
echo 'release-assets/*.blockmap'
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ winget install T3Tools.T3Code
brew install --cask t3-code
```

#### Debian, Ubuntu (`.deb`)

Download the `.deb` from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), then:

```bash
sudo apt install ./T3-Code-*.deb
```

#### Arch Linux (AUR)

Stable:
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.42",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
"main": "dist-electron/boot.cjs",
"scripts": {
"ensure:electron": "node scripts/ensure-electron-runtime.mjs",
"start": "node scripts/start-electron.mjs",
Expand Down
63 changes: 63 additions & 0 deletions apps/desktop/scripts/main-process-bundle.test.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as NodeChildProcess from "node:child_process";
import * as NodeFSP from "node:fs/promises";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
Expand Down Expand Up @@ -108,3 +109,65 @@ void import("./linux.ts").then(({ result }) => process.emit("ready", result));`,
await NodeFSP.rm(directory, { recursive: true, force: true });
}
});

it("loads the emitted packaged boot entry and backend cache preload", async () => {
const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-desktop-boot-"));
try {
const entries = ["src/boot.ts", "src/compileCache.ts"];
await NodeFSP.mkdir(NodePath.join(directory, "src"));
await Promise.all(
entries.map((entry) =>
NodeFSP.copyFile(new URL(`../${entry}`, import.meta.url), NodePath.join(directory, entry)),
),
);
assert.ok(Array.isArray(desktopConfig.pack));
for (const packConfig of desktopConfig.pack) {
if (!Array.isArray(packConfig.entry)) continue;
if (!packConfig.entry.some((entry) => entries.includes(entry))) continue;
await build({
...packConfig,
config: false,
cwd: directory,
tsconfig: false,
sourcemap: false,
onSuccess: undefined,
logLevel: "silent",
});
}
const outputDirectory = NodePath.join(directory, "dist-electron");
const fixture = `console.log(require('node:module').getCompileCacheDir() ? 'cached' : 'uncached');`;
await NodeFSP.writeFile(NodePath.join(outputDirectory, "main.cjs"), fixture);
await NodeFSP.writeFile(
NodePath.join(outputDirectory, "backend.mjs"),
`import { getCompileCacheDir } from 'node:module'; console.log(getCompileCacheDir() ? 'cached' : 'uncached');`,
);
for (const disabled of [false, true]) {
for (const args of [
[NodePath.join(outputDirectory, "boot.cjs")],
[
"--require",
NodePath.join(outputDirectory, "compileCache.cjs"),
NodePath.join(outputDirectory, "backend.mjs"),
],
]) {
const child = NodeChildProcess.spawnSync(process.execPath, args, {
encoding: "utf8",
env: {
...process.env,
APPIMAGE: "",
NODE_COMPILE_CACHE: undefined,
NODE_DISABLE_COMPILE_CACHE: disabled ? "1" : undefined,
XDG_CACHE_HOME: directory,
TMPDIR: directory,
TEMP: directory,
TMP: directory,
},
});
assert.equal(child.status, 0, child.stderr);
assert.equal(child.stdout.trim(), disabled ? "uncached" : "cached");
}
}
} finally {
await NodeFSP.rm(directory, { recursive: true, force: true });
}
});
2 changes: 1 addition & 1 deletion apps/desktop/scripts/smoke-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { resolveElectronLaunchCommand } from "./electron-launcher.mjs";

const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
const desktopDir = NodePath.resolve(__dirname, "..");
const mainJs = NodePath.resolve(desktopDir, "dist-electron/main.cjs");
const mainJs = NodePath.resolve(desktopDir, "dist-electron/boot.cjs");

console.log("\nLaunching Electron smoke test...");

Expand Down
67 changes: 67 additions & 0 deletions apps/desktop/src/app/DesktopAppActivation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ function request(requestId: string, platform: NodeJS.Platform): DesktopAppActiva
};
}

function startOkServer(target: ReturnType<typeof makeTarget>, userId: number | undefined) {
return startDesktopAppControlServer({
...target,
userId,
handle: async (input) => ({
version: 1,
requestId: input.requestId,
ok: true,
projectId: ProjectId.make("project-1"),
threadId: ThreadId.make("thread-1"),
}),
cancel: () => undefined,
onReclaimError: () => undefined,
}).then((server) => {
openServers.push(server);
return server;
});
}

function exchange(address: string, payload: DesktopAppActivationRequest) {
return new Promise<DesktopAppActivationResponse>((resolve, reject) => {
const socket = NodeNet.createConnection(address);
Expand Down Expand Up @@ -84,6 +103,7 @@ describe("desktop app control server", () => {
};
},
cancel: () => undefined,
onReclaimError: () => undefined,
});
openServers.push(server);

Expand Down Expand Up @@ -117,6 +137,7 @@ describe("desktop app control server", () => {
userId,
handle: () => new Promise(() => undefined),
cancel: resolveCanceled,
onReclaimError: () => undefined,
});
openServers.push(server);
const socket = NodeNet.createConnection(target.address);
Expand All @@ -137,4 +158,50 @@ describe("desktop app control server", () => {
});
}),
);

// Two desktop apps can share one state dir, such as nightly and a preview build.
it.effect("keeps a newer app's socket when an older app on the same state dir quits", () =>
Effect.gen(function* () {
const platform = yield* HostProcessPlatform;
const userId = yield* HostProcessUserId;
if (platform === "win32") return;
yield* Effect.promise(async () => {
const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-app-takeover-test-"));
const target = makeTarget(NodePath.join(root, "userdata"), platform, userId);
const older = await startOkServer(target, userId);
await startOkServer(target, userId);

await older.close();

await expect(
exchange(target.address, request("after-quit", platform)),
).resolves.toMatchObject({ ok: true, requestId: "after-quit" });
await NodeFSP.rm(root, { recursive: true, force: true });
});
}),
);

it.effect("binds its address again after the socket file is removed", () =>
Effect.gen(function* () {
const platform = yield* HostProcessPlatform;
const userId = yield* HostProcessUserId;
if (platform === "win32") return;
yield* Effect.promise(async () => {
const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-app-reclaim-test-"));
const target = makeTarget(NodePath.join(root, "userdata"), platform, userId);
const server = await startOkServer(target, userId);

await NodeFSP.unlink(target.address);
await server.reclaim();

await expect(
exchange(target.address, request("reclaimed", platform)),
).resolves.toMatchObject({
ok: true,
requestId: "reclaimed",
});
await NodeFSP.rm(root, { recursive: true, force: true });
});
}),
);
});
Loading
Loading