Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
5a43324
feat: add custom model names and option descriptors (#9807)
juliusmarminge Sep 4, 2026
087cfb8
fix(cursor): discover symlinked skills as package boundaries (#9420)
EzraBuild Sep 4, 2026
f33fdc9
fix(ssh): exec managed servers without npm wrappers (#9843)
juliusmarminge Sep 4, 2026
0dd5c64
fix(server): detect nested Git workspaces for checkpoints (#9842)
juliusmarminge Sep 4, 2026
4f1092c
fix(web): keep worktree origin preference visible (#9846)
juliusmarminge Sep 5, 2026
2e61301
fix(web): smooth settings sidebar transitions (#9811)
juliusmarminge Sep 5, 2026
4d3907f
feat(web): highlight visible settings sections (#9812)
juliusmarminge Sep 5, 2026
1963ca0
fix(web): keep the sidebar project filter across navigation (#9416)
Mnigos Sep 5, 2026
a5bbad9
fix(server): surface Claude safety model fallback notices instead of …
darahaas15 Sep 5, 2026
caf4981
fix(web): reload saved colors when reopening the theme editor (#9847)
juliusmarminge Sep 5, 2026
ed2bdbb
feat(desktop): import cookies from Safari (#7262)
juliusmarminge Sep 5, 2026
8faf031
fix(web): respect case in POSIX file links (#9309)
Michel-Liao Sep 5, 2026
fce8508
fix(server): surface a missing workspace folder instead of a spawn er…
kakismash Sep 5, 2026
b6f7268
fix: stop favicon requests for private link hosts on web and mobile (…
fe-franco Sep 5, 2026
2d5464a
fix(server): preserve native provider executable paths during updates…
juliusmarminge Sep 5, 2026
720e126
fix(web): restore composer expansion after tool calls (#9782)
t3dotgg Sep 5, 2026
2dca7a1
fix(client): explain possible network blocking for T3 Connect (#9783)
t3dotgg Sep 5, 2026
b5fb3fb
fix(desktop): isolate preview keyboard shortcuts from the host (#9840)
maria-rcks Sep 5, 2026
110bbe6
fix(prs): reuse GitHub data and defer optional reads (#9835)
maria-rcks Sep 5, 2026
c3cacea
perf(shared): skip duplicate PATH entries and per-probe tracing (#9618)
jadeva Sep 5, 2026
896fe82
fix(web): preserve focus and prioritize picker shortcuts (#9795)
maria-rcks Sep 5, 2026
82f64cd
fix(skills): support names beginning with digits (#9244)
amiralibg Sep 5, 2026
89ee69e
fix(server): advertise truecolor in the integrated terminal (#7680)
shubhxho Sep 5, 2026
940e823
fix(claude): surface usage-limit pauses in the thread (#7165)
vitalyiegorov Sep 5, 2026
ce4712d
fix: restore UX after performance improvements (#9799)
t3dotgg Sep 5, 2026
57a6b70
fix(codex): show what a file-change approval will change (#8669)
walid-baharwal Sep 5, 2026
4ade365
fix(ci): add fallback Ubuntu package mirrors (#9864)
maria-rcks Sep 5, 2026
03c6cd8
fix(web): copy text over plain HTTP (#8023)
Michel-Liao Sep 5, 2026
d7fe47f
fix(server): dismiss native questions when their turn ends (#9851)
juliusmarminge Sep 5, 2026
d28077e
fix(server): quote copied native provider update commands (#9856)
juliusmarminge Sep 5, 2026
1665d81
fix(release): install the correct Windows Spectre component (#9859)
maria-rcks Sep 5, 2026
1246146
fix(web): show retained runtime diagnostics in the work log (#9870)
juliusmarminge Sep 5, 2026
7f8cf30
fix(web): make sidebar project actions reachable by keyboard and scre…
akj Sep 5, 2026
de1b798
fix(preview): bound automation waits and screenshot captures (#4685)
juliusmarminge Sep 5, 2026
7ee52b0
fix(web): render and filter usage as environments respond (#9860)
juliusmarminge Sep 5, 2026
84b99f3
feat(web): edit usage prices across selected environments (#9861)
juliusmarminge Sep 5, 2026
ac90950
fix(mobile): keep usage tabs below the header when switching (#9876)
juliusmarminge Sep 5, 2026
363cde4
fix(connect): refresh HTTP credentials without reconnecting (#9594)
juliusmarminge Sep 5, 2026
b01771c
fix(web): keep file autosaves active after effect replay (#9878)
juliusmarminge Sep 5, 2026
a0eb239
fix(web): prioritize open panel pull request when copying (#9877)
maria-rcks Sep 5, 2026
9cb4017
fix(cli): resolve projects with missing workspace directories (#9885)
juliusmarminge Sep 5, 2026
931d41f
fix(web): sort equally merge-ready pull requests by diff size (#9887)
maria-rcks Sep 5, 2026
94cc815
fix(chat): show hours for long runs (#9894)
t3dotgg Sep 5, 2026
2fb99a7
fix(server): only run provider updates through the installer that own…
juliusmarminge Sep 5, 2026
df8e0eb
fix(web): discard stale highlights after file edits (#9902)
juliusmarminge Sep 5, 2026
fd77317
feat(web): recall sent prompts with the up arrow (#9173)
t3dotgg Sep 5, 2026
c7dc3cb
fix(server): keep mise-owned npm packages manual-only (#9927)
juliusmarminge Sep 5, 2026
f47a3fe
fix(settings): share restart continuation across environments (#9933)
maria-rcks Sep 5, 2026
7a089b2
fix(server): capture turn checkpoints after all edits finish (#9841)
juliusmarminge Sep 5, 2026
2fa5ef4
fix(web): load file grammar before enabling edits (#9947)
juliusmarminge Sep 5, 2026
e5a87e8
fix(web): deduplicate PR project filter choices (#9948)
juliusmarminge Sep 5, 2026
07d2497
test(web): keep provider field readers private (#9952)
juliusmarminge Sep 5, 2026
a399473
test(mobile): keep composer selection helper private (#9953)
juliusmarminge Sep 5, 2026
f530d7b
test(client-runtime): keep scoped key implementation private (#9955)
juliusmarminge Sep 5, 2026
6b87ce3
fix(web): reveal reselected diff files (#9951)
juliusmarminge Sep 5, 2026
eae770b
refactor(web): remove obsolete changed-files preview helpers (#9956)
juliusmarminge Sep 5, 2026
bc8584b
fix(web): give the browser keybinding notice breathing room (#9964)
juliusmarminge Sep 5, 2026
126ea5c
chore: configure Knip workspace audits (#9958)
juliusmarminge Sep 5, 2026
4a42fc6
refactor(web): prune unused UI and provider code (#9959)
juliusmarminge Sep 5, 2026
2759ef0
chore(mobile): remove obsolete widget wiring script (#9960)
juliusmarminge Sep 5, 2026
00d4618
chore: remove redundant root tooling dependencies (#9961)
juliusmarminge Sep 5, 2026
d2c3e2e
ci: reject unused files and dependencies with Knip (#9962)
juliusmarminge Sep 5, 2026
3c3a7fc
merge: sync upstream through d2c3e2e5d7
github-actions[bot] Sep 5, 2026
0e55165
fix(sync): add fork queue fields to new thread fixtures
github-actions[bot] Sep 5, 2026
cadb297
fix(sync): restore host normalize import and instance lookup export
github-actions[bot] Sep 5, 2026
3f6a36f
fix(sync): align Kimi maintenance and test layers with upstream APIs
github-actions[bot] Sep 5, 2026
8fcedb3
fix(sync): return structured thread-outbox load results in tests
github-actions[bot] Sep 5, 2026
bba773a
fix(ssh): keep inherited PATH when remote node is already usable
github-actions[bot] Sep 5, 2026
897c6e0
fix(sync): expect Kimi in the default provider registry list
github-actions[bot] Sep 5, 2026
719bdb5
fix(web): isolate file-editor highlight tests and raise the 60-edit b…
github-actions[bot] Sep 5, 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
22 changes: 22 additions & 0 deletions .github/actions/setup-apt-mirrors/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Setup APT mirrors
description: Configure Ubuntu package downloads with automatic mirror failover.
runs:
using: composite
steps:
- shell: bash
run: |
# Replace the existing Blacksmith mirror list as well as direct sources.
printf '%s\tpriority:%s\n' \
https://archive.ubuntu.com/ubuntu 1 \
https://mirrors.edge.kernel.org/ubuntu 2 \
https://mirror.math.princeton.edu/pub/ubuntu 3 \
| sudo tee /etc/apt/blacksmith-ubuntu-mirrors.txt > /dev/null

# APT's mirror transport retries each file against the next server.
sudo find /etc/apt -maxdepth 2 -type f \( -name '*.list' -o -name '*.sources' \) \
-exec sed -i -E \
's#https?://(([^/]+\.)?archive|security)\.ubuntu\.com/ubuntu/?#mirror+file:/etc/apt/blacksmith-ubuntu-mirrors.txt#g' {} +

# Move on to a fallback before an unreachable server exhausts the job.
printf '%s\n' 'Acquire::http::Timeout "15";' 'Acquire::https::Timeout "15";' \
| sudo tee /etc/apt/apt.conf.d/80-mirror-timeouts > /dev/null
16 changes: 14 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,22 @@ jobs:
- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

# Export cleanup is still a manual audit; files and dependencies have no baseline.
- name: Check unused files and dependencies
run: vp run knip:check

- name: Check
run: vp check

- name: Typecheck
run: vpr typecheck

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Build desktop pipeline
run: vp run build:desktop
Expand Down Expand Up @@ -88,8 +96,12 @@ jobs:
- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Test
run: vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/monorepo' test
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ jobs:
- name: Typecheck
run: vp run typecheck

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

Expand Down Expand Up @@ -524,14 +526,17 @@ jobs:
$setupExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe"
$proc = Start-Process -FilePath $setupExe `
-ArgumentList "modify", "--installPath", "`"$installPath`"", "--add", `
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64.Spectre", "--quiet", "--norestart" `
"Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre", "--quiet", "--norestart" `
-Wait -PassThru -NoNewWindow
if ($null -eq $proc -or $proc.ExitCode -ne 0) {
$code = if ($null -ne $proc) { $proc.ExitCode } else { 1 }
Write-Error "Visual Studio Installer failed with exit code $code"
exit $code
}

- uses: ./.github/actions/setup-apt-mirrors
if: matrix.platform == 'linux'

- name: Install Linux desktop build libraries
if: matrix.platform == 'linux'
shell: bash
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop/src/electron/ElectronShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ describe("ElectronShell", () => {
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens the Full Disk Access settings anchor", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const result = yield* electronShell.openSystemSettings("full-disk-access");

assert.equal(result, true);
assert.deepEqual(openExternalMock.mock.calls, [
["x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles"],
]);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens remote SSH editor URLs", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);
Expand Down
29 changes: 28 additions & 1 deletion apps/desktop/src/electron/ElectronShell.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { REMOTE_CAPABLE_EDITOR_IDS, remoteSchemeForEditor } from "@t3tools/contracts";
import {
REMOTE_CAPABLE_EDITOR_IDS,
remoteSchemeForEditor,
type SystemSettingsPane,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";

import * as Electron from "electron";

/**
* Deep links to individual System Settings panes. These are app-fixed, not
* renderer-supplied, so they skip `parseSafeExternalUrl` — which exists to keep
* arbitrary link schemes from reaching the OS handler — and open through their
* own path below. The pane rather than the URL crosses the IPC boundary, so a
* renderer can only ask for one of these known destinations.
*
* Full Disk Access uses the post-Ventura `PrivacySecurity.extension` anchor.
*/
const SYSTEM_SETTINGS_URLS: Record<SystemSettingsPane, string> = {
"full-disk-access":
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles",
};

const SAFE_WEB_PROTOCOLS = new Set(["http:", "https:"]);
// Editor URL schemes whose handler runs in the user's graphical session, so the desktop can open a
// file/folder or a Remote-SSH target even when the t3 server runs headless (e.g. a lingered systemd
Expand Down Expand Up @@ -66,6 +84,8 @@ export class ElectronShell extends Context.Service<
ElectronShell,
{
readonly openExternal: (rawUrl: unknown) => Effect.Effect<boolean>;
/** Opens a known System Settings pane by identifier, not by URL. */
readonly openSystemSettings: (pane: SystemSettingsPane) => Effect.Effect<boolean>;
readonly copyText: (text: string) => Effect.Effect<void>;
}
>()("@t3tools/desktop/electron/ElectronShell") {}
Expand All @@ -82,6 +102,13 @@ export const make = ElectronShell.of({
),
),
}),
openSystemSettings: (pane) =>
Effect.promise(() =>
Electron.shell.openExternal(SYSTEM_SETTINGS_URLS[pane]).then(
() => true,
() => false,
),
),
copyText: (text) =>
Effect.sync(() => {
Electron.clipboard.writeText(text);
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
getSystemLocale,
getWindowFullscreenState,
openExternal,
openSystemSettings,
probeRemoteEditors,
pickFolder,
pickProjectFavicon,
Expand Down Expand Up @@ -102,6 +103,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
yield* ipc.handle(pickOpenWithApplication);
yield* ipc.handle(resolveOpenWithPresentations);
yield* ipc.handle(openWith);
yield* ipc.handle(openSystemSettings);
yield* ipc.handle(probeRemoteEditors);
yield* ipc.handle(getUpdateState);
yield* ipc.handle(setUpdateChannel);
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
export const PICK_OPEN_WITH_APPLICATION_CHANNEL = "desktop:pick-open-with-application";
export const RESOLVE_OPEN_WITH_PRESENTATIONS_CHANNEL = "desktop:resolve-open-with-presentations";
export const OPEN_WITH_CHANNEL = "desktop:open-with";
export const OPEN_SYSTEM_SETTINGS_CHANNEL = "desktop:open-system-settings";
export const PROBE_REMOTE_EDITORS_CHANNEL = "desktop:probe-remote-editors";
export const MENU_ACTION_CHANNEL = "desktop:menu-action";
export const QUIT_SHORTCUT_CHANNEL = "desktop:quit-shortcut";
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/ipc/methods/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PickFolderOptionsSchema,
PRIMARY_LOCAL_ENVIRONMENT_ID,
REMOTE_CAPABLE_EDITOR_IDS,
SystemSettingsPaneSchema,
type DesktopEnvironmentBootstrap,
type PickedThemeFile,
} from "@t3tools/contracts";
Expand Down Expand Up @@ -298,6 +299,16 @@ export const openExternal = DesktopIpc.makeIpcMethod({
}),
});

export const openSystemSettings = DesktopIpc.makeIpcMethod({
channel: IpcChannels.OPEN_SYSTEM_SETTINGS_CHANNEL,
payload: SystemSettingsPaneSchema,
result: Schema.Boolean,
handler: Effect.fn("desktop.ipc.window.openSystemSettings")(function* (pane) {
const shell = yield* ElectronShell.ElectronShell;
return yield* shell.openSystemSettings(pane);
}),
});

export const probeRemoteEditors = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PROBE_REMOTE_EDITORS_CHANNEL,
payload: Schema.Undefined,
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {
resolveOpenWithPresentations: () =>
ipcRenderer.invoke(IpcChannels.RESOLVE_OPEN_WITH_PRESENTATIONS_CHANNEL),
openWith: (input) => ipcRenderer.invoke(IpcChannels.OPEN_WITH_CHANNEL, input),
openSystemSettings: (pane: string) =>
ipcRenderer.invoke(IpcChannels.OPEN_SYSTEM_SETTINGS_CHANNEL, pane),
probeRemoteEditors: () => ipcRenderer.invoke(IpcChannels.PROBE_REMOTE_EDITORS_CHANNEL, undefined),
onMenuAction: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => {
Expand Down
50 changes: 35 additions & 15 deletions apps/desktop/src/preview/BrowserImport/BrowserImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as BrowserSession from "../BrowserSession.ts";
import { ChromiumCookieReadError, readChromiumCookies } from "./ChromiumCookies.ts";
import type { CookieReadResult } from "./CookieDatabase.ts";
import { FirefoxCookieReadError, readFirefoxCookies } from "./FirefoxCookies.ts";
import { readSafariCookies, safariAccessDenied, SafariCookieReadError } from "./SafariCookies.ts";
import {
BROWSER_IMPORT_SOURCES,
resolveCookieDatabase,
Expand Down Expand Up @@ -92,6 +93,15 @@ const unavailableReason = Effect.fn("BrowserImport.unavailableReason")(function*
if (!definition.platforms.includes(context.platform)) return "unsupportedPlatform";
if (!(yield* isSourceInstalled(definition, context))) return "notInstalled";
if (yield* isSourceRunning(definition, context)) return "browserRunning";
// Safari's jar is found by `stat`, which TCC permits without Full Disk
// Access — so a Safari that lists as ready may still refuse the read. Probe
// the grant here, so the wizard can open on the permission step and a
// post-grant recheck can tell granted from still-denied, rather than only
// discovering it by attempting the import.
if (definition.engine === "safari") {
const jar = yield* resolveCookieDatabase(definition, context, ".");
if (jar !== undefined && (yield* safariAccessDenied(jar))) return "needsFullDiskAccess";
}
return undefined;
});

Expand Down Expand Up @@ -254,25 +264,29 @@ export const make = Effect.gen(function* BrowserImportMake() {
const userDataDirectory = definition.userDataDirectory(pathContext);
const read: Effect.Effect<
CookieReadResult,
ChromiumCookieReadError | FirefoxCookieReadError,
ChromiumCookieReadError | FirefoxCookieReadError | SafariCookieReadError,
FileSystem.FileSystem | Path.Path | Scope.Scope | ChildProcessSpawner.ChildProcessSpawner
> =
definition.engine === "firefox"
? readFirefoxCookies(databasePath).pipe(
definition.engine === "safari"
? readSafariCookies(databasePath).pipe(
Effect.map((cookies) => ({ cookies, undecryptable: 0, undecryptableHosts: [] })),
)
: readChromiumCookies({
cookieDatabasePath: databasePath,
keychainService: definition.keychainService,
keychainAccount: definition.keychainAccount,
linuxSecretApplication: definition.linuxSecretApplication,
...(platform === "win32" && userDataDirectory !== undefined
? {
windowsLocalStatePath: pathContext.path.join(userDataDirectory, "Local State"),
}
: {}),
platform,
});
: definition.engine === "firefox"
? readFirefoxCookies(databasePath).pipe(
Effect.map((cookies) => ({ cookies, undecryptable: 0, undecryptableHosts: [] })),
)
: readChromiumCookies({
cookieDatabasePath: databasePath,
keychainService: definition.keychainService,
keychainAccount: definition.keychainAccount,
linuxSecretApplication: definition.linuxSecretApplication,
...(platform === "win32" && userDataDirectory !== undefined
? {
windowsLocalStatePath: pathContext.path.join(userDataDirectory, "Local State"),
}
: {}),
platform,
});

const result = yield* read.pipe(
Effect.scoped,
Expand All @@ -289,6 +303,12 @@ export const make = Effect.gen(function* BrowserImportMake() {
Effect.fail(
new BrowserImportFailedError({ sourceId: definition.id, reason: "readFailed", cause }),
),
// Safari's reasons are already user-facing: a TCC refusal is the Full
// Disk Access prompt, anything else is a read failure.
SafariCookieReadError: (cause) =>
Effect.fail(
new BrowserImportFailedError({ sourceId: definition.id, reason: cause.reason, cause }),
),
}),
);

Expand Down
Loading
Loading