Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
175 commits
Select commit Hold shift + click to select a range
ffaa1db
feat: pin favorite projects and archive old ones (#26154)
t3dotgg Oct 31, 2026
a24ec90
fix(web): machine label no longer shifts between one and many machine…
t3dotgg Oct 31, 2026
793bbf4
fix(web): long titles no longer overflow the pull request list toolti…
t3dotgg Oct 31, 2026
2359643
fix(web): model picker stays open until dismissed, with a Show more r…
t3dotgg Oct 31, 2026
eef35b0
fix(clients): stop loading thread families when opening chats (#26762)
Yash-Singh1 Oct 31, 2026
9c0fb54
chore(release): prepare v0.1.6
t3-code-bot[bot] Oct 31, 2026
79f1fad
fix(server): bump Antigravity quota probe UA to hub/2.24.0 so Gemini …
LVC-Chakresh Oct 31, 2026
b970bcb
fix(opencode): use Big Pickle for default text generation (#22442)
UtkarshUsername Oct 31, 2026
44d91c5
test(scripts): identify the Windows native probe by executable (#21774)
UtkarshUsername Oct 31, 2026
15fe283
fix(web): effort picker shows Default or the checkmark, flush right (…
shivamhwp Oct 31, 2026
5fe18d7
fix(web): keep the model picker in place when its label changes (#28307)
juliusmarminge Oct 31, 2026
54c5f25
fix(device): use expo-device-hub 0.19.1 (#28346)
Yash-Singh1 Oct 31, 2026
575579d
fix(web): model picker stays put with long names and agent options (#…
t3dotgg Oct 31, 2026
8b74a69
fix(web): background command details open above their button (#28446)
t3dotgg Nov 1, 2026
8473d15
refactor(desktop): Drop the unused Linux --class switch (#14482)
mwolson Nov 1, 2026
7115a0b
perf(server): connects no longer wait on editor and SSH target discov…
saphid Nov 1, 2026
2872482
fix(vcs): look up pull request clone URLs on the pull request's host …
saphid Nov 1, 2026
90f0646
fix(mobile): images stop re-downloading when their URL is re-signed (…
flamboh Nov 1, 2026
0b75a85
fix(desktop): keep noisy shell probes from blocking PATH capture (#21…
Lucenx9 Nov 1, 2026
b8ca826
fix(server): preview status and snapshot never echo a data: page's co…
shivamhwp Nov 1, 2026
6daebdb
fix(mobile): keep reduced-motion views visible and aligned (#23230)
PixPMusic Nov 1, 2026
ec6020e
fix(server): match webhook existence filters on wildcard objects (#23…
yashranaway Nov 1, 2026
d52ee1b
fix(usage): preserve Claude monthly spend through CLIProxy (#23435)
yashranaway Nov 1, 2026
35041c2
fix(copy): clarify thread and source control settings (#24148)
extoci Nov 1, 2026
c8e78cb
fix(copy): clarify unavailable tasks and duplicate requests (#24149)
extoci Nov 1, 2026
0343c83
fix(web,mobile): lineage and agent rows show which account a subagent…
vitalyiegorov Nov 1, 2026
2c59bf6
fix(web,mobile): subagent cards show which account each subagent runs…
vitalyiegorov Nov 1, 2026
4f990c4
fix(mobile): release inactive draft Git status demand (#25254)
saphid Nov 1, 2026
d7a78f0
fix(shared): allow atomic saves through 40 symlinks (#25604)
Lucenx9 Nov 1, 2026
7808c1e
fix(server): device tools accept a host's label and reject unknown ho…
flamboh Nov 1, 2026
033a753
fix(web): threads on an unreachable environment no longer hide in the…
saphid Nov 1, 2026
01eb339
fix(desktop): allow slow backends to start before showing recovery (#…
shivamhwp Nov 1, 2026
6f2e48d
fix(mobile): keep Android thread title spacing consistent (#26666)
PixPMusic Nov 1, 2026
20f45bf
perf(server): avoid unused history reads during thread waits (#27032)
Adamulek123 Nov 1, 2026
f17126c
fix(web): sidebar metadata uses the theme's colors in dark mode (#27268)
flamboh Nov 1, 2026
bfd37b7
fix(web): Queued messages can be edited during Compact and send (#27310)
mwolson Nov 1, 2026
9fda652
fix(mobile): preserve chat scroll position on tab returns (#27393)
PixPMusic Nov 1, 2026
8f1e61c
perf(server): avoid unused history when acknowledging tasks (#27803)
Adamulek123 Nov 1, 2026
78aa0d5
feat(mcp): unlink every PR in given states with one call (#27925)
maria-rcks Nov 1, 2026
01bd0a2
feat(mcp): link several PRs to a thread with one call (#27994)
maria-rcks Nov 1, 2026
9f9eaff
perf(server): task waits skip unrelated sibling updates (#27920)
Adamulek123 Nov 1, 2026
b1bd064
fix(acp): keep turn spending out of context usage (#27927)
maria-rcks Nov 1, 2026
4e6b7d8
fix(web): explain when a theme hides environment artwork (#27936)
shivamhwp Nov 1, 2026
3d68b7a
fix(mobile): Android commit sheet scrolls to the commit buttons (#28272)
shivamhwp Nov 1, 2026
481ff3e
fix(cursor): avoid reporting unfinished tool calls as successful (#21…
only21mil Nov 1, 2026
0ed7b72
fix(gitlab): canceling pipelines no longer show as pending (#21543)
MarcoDotIO Nov 1, 2026
9b1802d
docs: explain how to enable the context window indicator (#21678)
birosrichard Nov 1, 2026
43d445b
fix(web): file preview videos keep their own aspect ratio (#21839)
NikitaMGrimm Nov 1, 2026
08a40a9
fix(server): an open question stays in the thread window, so its answ…
AdEx-Partners-DE Nov 1, 2026
9cb59bf
fix(web): mod+L focuses the browser address bar from a focused page (…
jnunemaker Nov 1, 2026
706bf34
fix(mobile): iOS terminal's Attach visible output clears the home ind…
Claudesaul Nov 1, 2026
253acde
fix(pi): show an extension dialog's title once (#22463)
only21mil Nov 1, 2026
655165d
fix(server): generated PR text reads source diffs instead of textconv…
only21mil Nov 1, 2026
5731c02
fix(server): queued delegated completions keep a newer model choice (…
f4llenz Nov 1, 2026
40f0649
fix(acp): turn token spend no longer blocks provider handoffs (#22608)
otavio Nov 1, 2026
3529723
fix(desktop): skip auxiliary macOS snapshot windows (#22624)
YoruSamaOG Nov 1, 2026
ff9f636
fix(provider-core): Chocolatey Claude installs stay manual-only and c…
robrab2000 Nov 1, 2026
85133dc
fix(web): PR diffs list a `.logic.test.ts` file beside the source it …
MarcoDotIO Nov 1, 2026
651e390
perf(desktop): speed up Hyprland and KDE SnapShots (#23137)
only21mil Nov 1, 2026
6d18edd
fix(mobile): Bitbucket and GitCafe approvals show as approved on the …
MarcoDotIO Nov 1, 2026
207faa2
fix(server): terminal labels keep the whole name of a command with sp…
MarcoDotIO Nov 1, 2026
549b651
fix(mobile): disabled send button is readable in Material You dark (#…
RVZO6 Nov 1, 2026
3331b93
fix(web): show linked pull request state in command palette rows (#23…
only21mil Nov 1, 2026
9d2634e
fix(desktop): read the Chrome cookie key off the main thread on macOS…
only21mil Nov 1, 2026
6262c9a
fix(screenshots): reject corrupt PNGs during validation (#23161)
only21mil Nov 1, 2026
bc799c4
fix(web): give non-Latin theme names distinct stable ids (#23162)
only21mil Nov 1, 2026
0671305
fix(server): generated commit messages use source patches (#23164)
only21mil Nov 1, 2026
f9dd721
fix(web): keep newer titles when an untouched header rename ends (#23…
only21mil Nov 1, 2026
e8b877c
fix(observability): record OTLP/JSON spans that leave out empty field…
only21mil Nov 1, 2026
e03b696
fix(server): keep the live search index when a refresh times out (#23…
only21mil Nov 1, 2026
d3f6ace
fix(web): respect provider permissions in bulk updates (#23183)
only21mil Nov 1, 2026
1b8516e
fix(mobile): cap live work-log summaries for long approval prompts (#…
only21mil Nov 1, 2026
d3315d2
fix(web): keep project name edits open during IME composition (#23231)
only21mil Nov 1, 2026
3d74f39
fix(server): put the updated runtime on the macOS service PATH (#23239)
only21mil Nov 1, 2026
a732caa
fix(server): Steer on a queued message keeps the model picked for it …
only21mil Nov 1, 2026
2a4d128
fix(web): line up the Date and Time fields in custom snooze (#23360)
voida6 Nov 1, 2026
1c8c0d8
fix(ssh): stop asking for a password when the SSH agent can't sign (#…
thepriceisright Nov 1, 2026
b0aa468
fix(web): copy and save images embedded as data URLs (#23621)
AkiVonAkira Nov 1, 2026
e5a2031
fix(server): review guide stops no longer show the same lines twice (…
only21mil Nov 1, 2026
b4ae3c8
fix(muse): list background children under their turn item's native id…
koushikxd Nov 1, 2026
cedf8b4
fix(web): format inline code when the opening backtick is typed last …
Saikrishna1876 Nov 1, 2026
c3303fb
fix(pi): log oversized RPC records instead of dropping them silently …
only21mil Nov 1, 2026
c2dc2ea
fix(web,mobile): Hebrew and Arabic read right to left in questions, c…
Dananz Nov 1, 2026
665351f
fix(web): show readable errors when a stack action fails (#24118)
only21mil Nov 1, 2026
5ffcb86
fix(web): usage limit toast shows the Limited icon (#24260)
fixfon Nov 1, 2026
846de87
fix(server): t3 update no longer deletes the runtime its launcher run…
only21mil Nov 1, 2026
16233e7
fix(web): typed answer moved to the draft by an option click is annou…
voida6 Nov 1, 2026
c6bde56
fix(relay): long CJK titles no longer stop Live Activity updates (#24…
only21mil Nov 1, 2026
ef6e05e
fix(themes): accept VS Code JSONC imports (#25375)
mayesidev Nov 1, 2026
503bf77
fix(web): prevent sidebar shelf animations from overlapping headers (…
brandon-julio-t Nov 1, 2026
1ad7861
fix(web): keep the default instance's draft model off custom instance…
matthias-scale Nov 1, 2026
5ff1482
fix(acp): cancelled plan steps no longer keep the plan running (#25564)
MarcoDotIO Nov 1, 2026
9ee7da6
fix(mobile): handle image drops in the Android composer (#25573)
12ya Nov 1, 2026
7370b92
fix(server): run_scheduled_task_now says why a run was refused (#25647)
SouthCarpet Nov 1, 2026
213b391
fix(web): composer emphasis shortcuts ignore delimiters inside inline…
egefeyzioglu Nov 1, 2026
b5ced43
fix(web): local visit migration preserves mark unread (#25746)
Michaelihc Nov 1, 2026
c3f0a6c
fix(provider-acp-registry): reopening a Kimi thread no longer collaps…
AKolenda Nov 1, 2026
dcd9adc
docs(readme): list fx as a supported provider (#25974)
DarlingtonDeveloper Nov 1, 2026
cbd43b6
fix(web): PR links at launch open the PR view with the local environm…
NotAnNvidiaFan Nov 1, 2026
1dacdb9
fix(web): sidebar jump hints are readable and sit in the status slot …
G-R3 Nov 1, 2026
6525e4c
fix(web): maximize button no longer jumps when the right panel closes…
Claudesaul Nov 1, 2026
28bfae0
fix(acp): a lost or locked Copilot session restarts on the next messa…
ianrowe12 Nov 1, 2026
d1049c4
perf(web): terminal snapshots reuse WASM memory views (#26717)
hieu-lee Nov 1, 2026
17c837c
fix(acp): Plan mode switches Copilot into its native plan mode (#26951)
Radu028 Nov 1, 2026
22ee8a4
fix(cursor): ignore a stale Keychain login when Cursor is not install…
riccardopll Nov 1, 2026
d64888f
fix(web): Escape while recording a shortcut no longer closes Settings…
treiber Nov 1, 2026
e8c70f5
fix(mobile): a closed empty search stays closed after returning (#27184)
camchis Nov 1, 2026
623344a
fix(antigravity): accept empty startup skill directories (#27191)
Gieveppix Nov 1, 2026
c404763
fix(mobile): TalkBack reads the message field before Send (#27249)
nibarAhmed Nov 1, 2026
6866a2d
fix(cli): trace summary no longer prints near-second durations as "10…
MarcoDotIO Nov 1, 2026
0743ba5
fix(shared): branch names keep accented letters as plain letters (#27…
MarcoDotIO Nov 1, 2026
1a2983f
fix(provider-core): provider updates find npm on the provider's custo…
ashx-j Nov 1, 2026
e0fa102
fix(web): changing the sidebar project filter starts the list at the …
Mnigos Nov 1, 2026
cae3ced
fix(web): find the fallback model in Settings search (#27376)
voltcrash Nov 1, 2026
868524f
fix(web): Backspace no longer re-nests a checkbox under a bullet (#27…
Geuni620 Nov 1, 2026
6d584fa
perf(server): reuse compiled modules for CLI commands (#27433)
voltcrash Nov 1, 2026
fbe137d
fix(server): preview tab list shows desktop tabs that loaded before t…
camchis Nov 1, 2026
e71f414
fix(web): Esc in the theme color picker restores the previous color (…
drdator Nov 1, 2026
6977721
fix(shared): preserve escaped trailing whitespace in CLI arguments (#…
macodev00 Nov 1, 2026
c018660
fix(shared): preserve surrogate pairs in truncated CSV cells (#27497)
macodev00 Nov 1, 2026
84ad7ae
fix(gitlab): report an expired glab OAuth login as an auth error (#27…
WalshyDev Nov 1, 2026
854247e
fix(web): expanded SVG diagrams fill the image viewer (#27519)
melbaldove Nov 1, 2026
cb1de7b
fix(web): configure reasoning for the fallback model (#27593)
BelKed Nov 1, 2026
dc9b1a5
fix(web): keep diff base selector available after errors (#27635)
NathanGlasby Nov 1, 2026
cbb0417
fix(mobile): resume continues a usage-limited thread before its queue…
AbdelrahmanTamer21 Nov 1, 2026
f702f89
fix(client): a new thread's content no longer stays blank after an ea…
willadamskeane Nov 1, 2026
aa8dbbe
fix(web): skill labels show the skill's own name (#27741)
juuulianx Nov 1, 2026
5cc1985
fix(server): disclose truncated history in imported threads (#27743)
voltcrash Nov 1, 2026
6843eff
fix(opencode): recover Go limits after a startup timeout (#27757)
voltcrash Nov 1, 2026
1917c8a
fix(server): worktree threads get their generated branch name when a …
MarcoDotIO Nov 1, 2026
5bb50dc
fix(server): restart continuation checks before repeating side effect…
xlurie Nov 1, 2026
6dddccb
fix(shared): /Model with a query still opens the model picker (#27837)
samkelly880 Nov 1, 2026
aa33734
fix(server): empty primary cookie no longer blocks bearer auth (#27848)
samkelly880 Nov 1, 2026
a62452a
perf(mobile): text files open in half the time on a remote host (#27850)
camchis Nov 1, 2026
bc448b8
fix(web): composer fallback keeps the effort of a model saved under a…
mkantautas Nov 1, 2026
dabab45
perf(server): skip no-op settlement decisions (#27897)
omar7550 Nov 1, 2026
34cfd24
fix(codex): a failed write to the app-server no longer leaves request…
trevoedwards Nov 1, 2026
626d602
perf(server): projects without a repository stop spawning git on ever…
Pyseph Nov 1, 2026
4773ab7
fix(server): scheduled tasks start from the default branch, not alway…
tuanvumaihuynh Nov 1, 2026
ef4c658
fix(server): subagent threads no longer inherit a pending title job (…
Dreighto Nov 1, 2026
77a728e
fix(web): show mixed automatic defaults across environments (#28005)
jtatum Nov 1, 2026
1794830
fix(client): the folder browser shows folders added or removed while …
Umais-Adeed Nov 1, 2026
fad507c
fix(mobile): chat folds stay closed until tapped, as on web and deskt…
Umais-Adeed Nov 1, 2026
71d52eb
fix(web): align composer context icons and space their labels (#28083)
voltcrash Nov 1, 2026
5651d76
fix(web): horizontal scrolling inside diff and file panes no longer s…
EtanHey Nov 1, 2026
1f9d4de
fix(server): remember T3 Connect HTTP/2 fallback across restarts (#28…
voltcrash Nov 1, 2026
7f443ab
fix(server): provider updates no longer time out mid-download (#28132)
Ajraba04 Nov 1, 2026
4c91892
fix(server): delegate_task accepts the inherited options orchestrator…
EtanHey Nov 1, 2026
be58795
fix(mobile): finished threads leave Working once the grace ends (#28169)
playcations Nov 1, 2026
c77f755
fix(web): sidebar drops a deleted project's filter with the local env…
NotAnNvidiaFan Nov 1, 2026
f513369
fix(web): terminal URLs like redis:// no longer become broken file li…
tiliakoos Nov 1, 2026
dc102e5
fix(web): skill menus show loading while project skills arrive (#28209)
NotAnNvidiaFan Nov 1, 2026
77c885e
fix(desktop): SnapShots keep working on Hyprland after an app update …
Rhinomcd Nov 1, 2026
6717d92
fix(web): Tab completes folder names in the add-project browser (#28243)
morahan Nov 1, 2026
9ff0fac
fix(web): make Changes and PR Code file lists resizable (#28244)
wrymn Nov 1, 2026
2f777bd
perf(web): main thread updates no longer re-render an open side chat …
Yuruzuu Nov 1, 2026
6d25fa1
perf(server): skip unused parent data when forking threads (#28265)
Yuruzuu Nov 1, 2026
13c3c2e
perf(server): stop summarizing side chat context at its cap (#28266)
Yuruzuu Nov 1, 2026
39bca53
perf(web): typing in a thread no longer re-renders sidebar drafts (#2…
Yuruzuu Nov 1, 2026
ee29f11
fix(scripts): Alchemy reference sync reads the catalog version (#28268)
only21mil Nov 1, 2026
96b0377
docs(mobile): mobile README links the store apps instead of calling t…
only21mil Nov 1, 2026
35df72f
perf(web): render minimap highlights without extra nodes (#28270)
Yuruzuu Nov 1, 2026
906156b
fix(ci): a change to the T3 Connect troubleshooting guide runs the se…
only21mil Nov 1, 2026
cc5760a
fix(scripts): report:renders works on machines without ~/.cache (#28274)
only21mil Nov 1, 2026
c9f47c1
docs: composer banner guidance names a consumer that still exists (#2…
only21mil Nov 1, 2026
5732bbe
docs(mobile): name the Android 16 Agent Live Updates switch in the no…
only21mil Nov 1, 2026
ce48e45
fix(web): prevent composer image thumbnail flicker (#28499)
Yash-Singh1 Nov 1, 2026
955e2bf
fix(clients): questions always move a thread out of Working (#28515)
t3dotgg Nov 1, 2026
4ac97be
feat(scheduler): run a scheduled task once at a specific date and tim…
t3dotgg Nov 1, 2026
7195b69
fix(server): stopping a Claude turn no longer hangs without an acknow…
yaacovcorcos Nov 1, 2026
6328c84
feat(mcp): preview_open goes straight to a dev-server port (#28027)
maria-rcks Nov 1, 2026
ce9e016
fix(server): interrupted scheduled task runs no longer stay busy (#28…
t3dotgg Nov 1, 2026
37d7615
fix(desktop): preserve host focus on preview pointer presses (#28549)
Yash-Singh1 Nov 1, 2026
b50d8a1
fix(web): add spacing above PR detail metadata (#28573)
Yash-Singh1 Nov 1, 2026
e2d898a
chore: merge upstream/main into fork
adampeterhiggins Nov 1, 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
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions .github/scripts/ci-changes.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const EXTRA_OWNERS = [
[/^native\/browser-secret\//, ["@t3tools/desktop"]],
[/^native\/kde-snap-shot\//, ["@t3tools/desktop"]],
[/^native\/libghostty-vt\//, ["@t3tools/web"]],
// apps/server/src/cloud/ConnectReachability.test.ts checks the guide's headings.
[/^docs\/user\/t3-connect-troubleshooting\.md$/, ["t3"]],
];
const RUST = /^native\/(resource-monitor|kde-snap-shot|hyprland-snap-shot)\//;

Expand Down Expand Up @@ -92,11 +94,11 @@ function readWorkspace() {

// [] means inert; null means unknown and requires every job.
function ownersOf(file, packages) {
if (INERT.some((pattern) => pattern.test(file))) return [];
const extra = EXTRA_OWNERS.flatMap(([pattern, names]) => (pattern.test(file) ? names : []));
if (INERT.some((pattern) => pattern.test(file))) return extra;
// A manifest can change dependencies or versions other packages read.
if (path.posix.basename(file) === "package.json") return null;
if (RUNS_EVERYTHING.some((pattern) => pattern.test(file))) return null;
const extra = EXTRA_OWNERS.flatMap(([pattern, names]) => (pattern.test(file) ? names : []));
// scripts/build-desktop-artifact.test.ts reads the crates' sources.
if (RUST.test(file)) return ["@t3tools/scripts", ...extra];

Expand Down
2 changes: 1 addition & 1 deletion .macroscope/check-run-agents/ui-consistency.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The reference for how this codebase wants UI built is the composer banner system

- `ComposerBanner.tsx` exports one object of small slot components (`Root`, `Row`, `Icon`, `Content`, `Actions`, `Dismiss`, `Children`, and so on). Each slot owns its own Tailwind classes, exposes a `data-slot` attribute, takes `className` and spreads the rest of its props, and uses `cn` so callers can adjust without overriding. Variants and density are props on `Root`, not ad hoc class strings at call sites. Where a slot needs a real control it renders `Button` from `ui/` rather than a styled `<button>`.
- `ComposerBannerStack.tsx` composes those slots into the ordered stack and owns only stack behavior (priority, expand and collapse, dismiss transitions).
- Consumers such as `ComposerActivityStatus.tsx`, `ComposerStashBadge.tsx`, and `ComposerPlanFollowUpBanner.tsx` are a few lines of `<ComposerBanner.Row><ComposerBanner.Icon/><ComposerBanner.Content>…` with no styling of their own.
- Consumers such as `ComposerPlanFollowUpBanner.tsx` are a few lines of `<ComposerBanner.Row><ComposerBanner.Icon/><ComposerBanner.Content>…` and style only their own content.

Hold new and changed UI to that shape:

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

T3 Code is an "agent harness control surface". It enables control of the agents on your machine with a best-in-class mobile app ([iOS](https://apps.apple.com/us/app/t3-code-remote-claude-more/id6787819824), [Android](https://play.google.com/store/apps/details?id=com.t3tools.t3code)), [web app](https://app.t3.codes) and [Electron-based desktop app](https://t3.codes).

Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, and Google Antigravity. If they're set up on your computer, T3 Code can control them.
Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, Google Antigravity, and fx. If they're set up on your computer, T3 Code can control them.

## "Wait, what are you selling me?"

Expand All @@ -17,14 +17,15 @@ We wanted something performant, remote-ready, and truly open. If we ever go the
## Installation

> [!WARNING]
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, and Antigravity. Install and authenticate at least one provider before use:
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, Antigravity, and fx. Install and authenticate at least one provider before use:
>
> - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login`
> - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login`
> - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login`
> - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login`
> - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login`
> - Antigravity: enable it in Settings, then use **Install Antigravity** and **Sign in with Google**. No CLI is required.
> - fx: install [fx](https://fx.sh) and run `fx login` on the machine running the T3 Code server ([setup guide](./docs/user/providers-fx.md))

### Command line

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.1.5",
"version": "0.1.6",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/app/DesktopEarlyElectronStartup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type EarlyLinuxElectronOptionsInput = EarlyDesktopSettingsInput;

export interface EarlyLinuxElectronOptions {
readonly isDevelopment: boolean;
readonly linuxWmClass: string;
readonly linuxDesktopEntryName: string;
readonly passwordStore: LinuxPasswordStoreSwitch | null;
}
Expand Down Expand Up @@ -146,7 +145,6 @@ export function resolveEarlyLinuxElectronOptions(
const isDevelopment = isDevelopmentEnvironment(input.env);
return {
isDevelopment,
linuxWmClass: isDevelopment ? "t3code-dev" : "t3code",
linuxDesktopEntryName: resolveLinuxDesktopEntryName(isDevelopment),
passwordStore: resolveLinuxPasswordStoreSwitch({
preference,
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/app/DesktopEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export class DesktopEnvironment extends Context.Service<
readonly displayName: string;
readonly appUserModelId: string;
readonly linuxDesktopEntryName: string;
readonly linuxWmClass: string;
readonly linuxApplicationsDir: string;
readonly desktopLauncherPath: Option.Option<string>;
readonly appImagePath: Option.Option<string>;
Expand Down Expand Up @@ -232,7 +231,6 @@ const make = Effect.fn("desktop.environment.make")(function* (
isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code",
),
linuxDesktopEntryName: resolveLinuxDesktopEntryName(isDevelopment),
linuxWmClass: isDevelopment ? "t3code-dev" : "t3code",
linuxApplicationsDir,
desktopLauncherPath: config.desktopLauncherPath,
appImagePath: config.appImagePath,
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const makeEnvironment = (path: Path.Path, overrides: Record<string, unknown> = {
isDevelopment: false,
displayName: "T3 Code (Alpha)",
linuxDesktopEntryName: "com.t3tools.T3Code.desktop",
linuxWmClass: "t3code",
linuxApplicationsDir: "/home/alice/.local/share/applications",
desktopLauncherPath: Option.none(),
appImagePath: Option.some("/home/alice/Applications/T3-Code.AppImage"),
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/app/DesktopPreReadyPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ export const make = Effect.gen(function* () {
} catch {}
// Chromium caches portal identity; set it before any asynchronous initialization.
Electron.app.setDesktopName(linux.linuxDesktopEntryName);
Electron.app.commandLine.appendSwitch("class", linux.linuxWmClass);
if (linux.passwordStore !== null && linuxPasswordStoreCommandLine === null) {
Electron.app.commandLine.appendSwitch("password-store", linux.passwordStore);
}
Expand Down
38 changes: 38 additions & 0 deletions apps/desktop/src/backend/DesktopBackendManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,4 +631,42 @@ describe("DesktopBackendManager", () => {
}).pipe(Effect.provide(TestClock.layer())),
),
);

it.effect("waits out a slow startup and reports one that never becomes ready", () =>
Effect.scoped(
Effect.gen(function* () {
const reports = yield* Queue.unbounded<DesktopBackendManager.BackendStartupFailure>();
const startInstance = (status: Ref.Ref<number>, onReady?: Effect.Effect<void>) =>
makeTestInstance({
spawnerLayer: Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make(() =>
Effect.succeed(makeProcess({ exitCode: Effect.never })),
),
),
httpClientLayer: layerHttpClient((request) =>
Ref.get(status).pipe(Effect.map((code) => responseForRequest(request, code))),
),
onStartupFailure: (failure) => Queue.offer(reports, failure).pipe(Effect.asVoid),
...(onReady ? { onReady } : {}),
}).pipe(Effect.tap((instance) => instance.start));

const slowStatus = yield* Ref.make(503);
const ready = yield* Deferred.make<void>();
yield* startInstance(slowStatus, Deferred.succeed(ready, undefined).pipe(Effect.asVoid));
yield* TestClock.adjust(Duration.seconds(66));
yield* Ref.set(slowStatus, 200);
yield* TestClock.adjust(Duration.millis(100));
yield* Deferred.await(ready);
assert.isTrue(Option.isNone(yield* Queue.poll(reports)));

yield* startInstance(yield* Ref.make(503));
yield* TestClock.adjust(Duration.seconds(179));
assert.isTrue(Option.isNone(yield* Queue.poll(reports)));
yield* TestClock.adjust(Duration.seconds(1));
const failure = yield* Queue.take(reports);
assert.equal(failure.reason, "The server started but did not become ready in time.");
}).pipe(Effect.provide(TestClock.layer())),
),
);
});
3 changes: 2 additions & 1 deletion apps/desktop/src/backend/DesktopBackendManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts";
const INITIAL_RESTART_DELAY = Duration.millis(500);
const MAX_RESTART_DELAY = Duration.seconds(10);
const MAX_PREFLIGHT_FAILURE_ATTEMPTS = 5;
const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(1);
// Large state databases can take over a minute to become ready.
const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3);
const DEFAULT_BACKEND_READINESS_INTERVAL = Duration.millis(100);
const DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT = Duration.seconds(1);
// Match the service launcher's grace period so backends can stop provider sessions.
Expand Down
23 changes: 18 additions & 5 deletions apps/desktop/src/preview/BrowserImport/ChromiumKeys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,17 @@ import {
import { LinuxBrowserSecretPath } from "./LinuxBrowserSecret.ts";
import { BROWSER_IMPORT_SOURCES } from "./Sources.ts";

const { getPassword } = vi.hoisted(() => ({ getPassword: vi.fn<() => string | null>() }));
const { getPassword } = vi.hoisted(() => ({
getPassword: vi.fn<() => Promise<string | undefined>>(),
}));

vi.mock("@napi-rs/keyring", () => ({
Entry: class {
getPassword() {
throw new Error("synchronous keychain read");
}
},
AsyncEntry: class {
getPassword = getPassword;
},
}));
Expand Down Expand Up @@ -91,18 +98,24 @@ describe("macOS Chromium secrets", () => {

it.effect("derives the cookie key from the keychain secret", () =>
Effect.gen(function* () {
getPassword.mockReturnValue("macos-secret");
getPassword.mockResolvedValue("macos-secret");
const keys = yield* resolveChromiumKeys(request);
expect(keys.cbcV10?.toString("hex")).toBe("3df7306fb1eac353289565a2f6b64f74");
}).pipe(Effect.provide(layerNoProcesses)),
);

it.effect("reports a keychain entry the async read resolves as undefined", () =>
Effect.gen(function* () {
getPassword.mockResolvedValue(undefined);
const error = yield* resolveChromiumKeys(request).pipe(Effect.flip);
expect(error.reason).toBe("keychainItemMissing");
}).pipe(Effect.provide(layerNoProcesses)),
);

it.effect("preserves a denied keychain approval", () =>
Effect.gen(function* () {
const denied = new Error("User denied access");
getPassword.mockImplementation(() => {
throw denied;
});
getPassword.mockRejectedValue(denied);
const error = yield* resolveChromiumKeys(request).pipe(Effect.flip);
expect(error.reason).toBe("needsKeychainApproval");
expect(error.cause).toBe(denied);
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ const readKeychainSecret = Effect.fn("ChromiumKeys.readKeychainSecret")(function
try: () => import("@napi-rs/keyring"),
catch: (cause) => new ChromiumKeyError({ reason: "keychainUnavailable", cause }),
});
const secret = yield* Effect.try({
try: () => new Keyring.Entry(service, account).getPassword(),
const secret = yield* Effect.tryPromise({
try: () => new Keyring.AsyncEntry(service, account).getPassword(),
catch: (cause) => {
const message = String((cause as { message?: unknown } | undefined)?.message ?? "");
const missing = /no (matching )?entry|not found/i.test(message);
Expand All @@ -70,7 +70,7 @@ const readKeychainSecret = Effect.fn("ChromiumKeys.readKeychainSecret")(function
});
},
});
if (secret === null || secret === "") {
if (!secret) {
return yield* new ChromiumKeyError({ reason: "keychainItemMissing" });
}
return secret;
Expand Down
4 changes: 1 addition & 3 deletions apps/desktop/src/preview/DesktopBrowserHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,7 @@ export const make = Effect.gen(function* () {
if (tab.relay) return tab.relay;
const { webContents, debugger: debuggee } = tab.debuggee;
const send = (method: string, params: Record<string, unknown>, sessionId?: string) =>
sessionId === undefined
? sendPageCommand(webContents, debuggee, method, params)
: debuggee.sendCommand(method, params, sessionId);
sendPageCommand(webContents, debuggee, method, params, sessionId);
const detachedOwners = new Set<string>();
/** Viewers and recordings stream; the agent's own sessions never do. */
const streamingOwners = new Set<string>();
Expand Down
Loading