Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dc38f95
[sync] Remove Claude subscription-based model adjustment (#1899)
boggedbrush Apr 18, 2026
488fdef
[sync] Handle deleted git directories as non-repositories (#1907)
boggedbrush Apr 18, 2026
73eafb5
[sync-adapted] Coalesce status refreshes by remote (#1940)
boggedbrush Apr 18, 2026
88707cf
[sync] fix: quote editor launch args on Windows to support paths with…
boggedbrush Apr 18, 2026
78be17a
[sync] fix: Align token usage metrics for both Claude and Codex (#1943)
boggedbrush Apr 18, 2026
53cad2d
[sync] fix: lost provider session recovery (#1938)
boggedbrush Apr 18, 2026
dcc8bbf
[sync-adapted] Refresh git status after branch rename and worktree se…
boggedbrush Apr 18, 2026
0808aba
[sync] Improve shell PATH hydration and fallback detection (#1799)
boggedbrush Apr 18, 2026
63ad15f
[sync-adapted] Backfill projected shell summaries and stale approval …
boggedbrush Apr 18, 2026
a06b35c
[sync] fix(server): extend negative repository identity cache ttl (#2…
boggedbrush Apr 18, 2026
15d217c
[sync] feat: Add Kiro editor support to open picker (#1974)
boggedbrush Apr 18, 2026
2f73bb4
[sync] fix: prevent user-input activities from leaking into pending a…
boggedbrush Apr 18, 2026
6302167
chore(sync): add sync log and clean validation issues
boggedbrush Apr 18, 2026
be7628c
docs(sync): record PR link for 2026-04-17 upstream sync
boggedbrush Apr 18, 2026
3828c47
[sync] Clean up invalid pending approval projections (#2106)
boggedbrush Apr 18, 2026
59fe8c1
fix(server): restore shell summary migration
boggedbrush Apr 18, 2026
15cc8c8
[sync-adapted] Honor gitignored workspace search and preserve provide…
boggedbrush Apr 18, 2026
119dd9a
[sync-frontend] Restore terminal toggle shortcut from focused terminal
boggedbrush Apr 18, 2026
adf75c6
docs(sync): record 2026-04-18 upstream sync review
boggedbrush Apr 18, 2026
3c6bcf4
chore(sync): refresh 2026-04-19 upstream review
boggedbrush Apr 19, 2026
ca6806a
[sync-adapted] Port filesystem browse for project add
boggedbrush Apr 19, 2026
f23a1e8
fix(contracts): disable tsdown dts bundling
boggedbrush Apr 19, 2026
f7c93db
Fix Windows dev server startup
boggedbrush Apr 19, 2026
3cf27df
chore(turbo): pass through PATHEXT (#2184)
adammansfield Apr 20, 2026
7cbb590
fix(server): prevent probeClaudeCapabilities from wasting API request…
reasv Apr 20, 2026
85bba65
Expand leading ~ in Codex home paths before exporting CODEX_HOME (#2210)
altjx Apr 20, 2026
d3fbe24
sync: port bounded upstream runtime fixes
boggedbrush Apr 20, 2026
13ea716
fix(server): resolve review regressions in session recovery and diff …
boggedbrush Apr 20, 2026
12cc341
fix(server): boot cli without top-level await
boggedbrush Apr 20, 2026
290c92c
feat(web): add fullscreen project picker preference
boggedbrush Apr 20, 2026
69bd860
feat(web): finish model picker redesign
boggedbrush Apr 20, 2026
24da89e
docs(sync): record 2026-04-21 upstream review
boggedbrush Apr 21, 2026
6f5f4f6
sync: port upstream provider probe fixes
boggedbrush Apr 22, 2026
40cd204
sync: port upstream runtime hardening
boggedbrush Apr 23, 2026
b2dce34
docs(sync): record 2026-04-24 upstream review
boggedbrush Apr 24, 2026
9c2be10
docs(sync): correct 2026-04-24 PR link
boggedbrush Apr 24, 2026
56f95ba
docs(sync): record 2026-04-25 upstream review
boggedbrush Apr 25, 2026
873512d
docs(sync): record 2026-04-27 upstream review
boggedbrush Apr 27, 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
74 changes: 70 additions & 4 deletions apps/desktop/src/syncShellEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@ describe("syncShellEnvironment", () => {
it("hydrates PATH and missing SSH_AUTH_SOCK from the login shell on macOS", () => {
const env: NodeJS.ProcessEnv = {
SHELL: "/bin/zsh",
PATH: "/usr/bin",
PATH: "/Users/test/.local/bin:/usr/bin",
};
const readEnvironment = vi.fn(() => ({
PATH: "/opt/homebrew/bin:/usr/bin",
SSH_AUTH_SOCK: "/tmp/secretive.sock",
HOMEBREW_PREFIX: "/opt/homebrew",
}));

syncShellEnvironment(env, {
platform: "darwin",
readEnvironment,
});

expect(readEnvironment).toHaveBeenCalledWith("/bin/zsh", ["PATH", "SSH_AUTH_SOCK"]);
expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin");
expect(readEnvironment).toHaveBeenCalledWith("/bin/zsh", [
"PATH",
"SSH_AUTH_SOCK",
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
]);
expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin:/Users/test/.local/bin");
expect(env.SSH_AUTH_SOCK).toBe("/tmp/secretive.sock");
expect(env.HOMEBREW_PREFIX).toBe("/opt/homebrew");
});

it("preserves an inherited SSH_AUTH_SOCK value", () => {
Expand Down Expand Up @@ -77,11 +87,67 @@ describe("syncShellEnvironment", () => {
readEnvironment,
});

expect(readEnvironment).toHaveBeenCalledWith("/bin/zsh", ["PATH", "SSH_AUTH_SOCK"]);
expect(readEnvironment).toHaveBeenCalledWith("/bin/zsh", [
"PATH",
"SSH_AUTH_SOCK",
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
]);
expect(env.PATH).toBe("/home/linuxbrew/.linuxbrew/bin:/usr/bin");
expect(env.SSH_AUTH_SOCK).toBe("/tmp/secretive.sock");
});

it("falls back to launchctl PATH on macOS when shell probing does not return one", () => {
const env: NodeJS.ProcessEnv = {
SHELL: "/opt/homebrew/bin/nu",
PATH: "/usr/bin",
};
const readEnvironment = vi
.fn()
.mockImplementationOnce(() => {
throw new Error("unknown flag");
})
.mockImplementationOnce(() => ({}));
const readLaunchctlPath = vi.fn(() => "/opt/homebrew/bin:/usr/bin");
const logWarning = vi.fn();

syncShellEnvironment(env, {
platform: "darwin",
readEnvironment,
readLaunchctlPath,
userShell: "/bin/zsh",
logWarning,
});

expect(readEnvironment).toHaveBeenNthCalledWith(1, "/opt/homebrew/bin/nu", [
"PATH",
"SSH_AUTH_SOCK",
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
]);
expect(readEnvironment).toHaveBeenNthCalledWith(2, "/bin/zsh", [
"PATH",
"SSH_AUTH_SOCK",
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
]);
expect(readLaunchctlPath).toHaveBeenCalledTimes(1);
expect(logWarning).toHaveBeenCalledWith(
"Failed to read login shell environment from /opt/homebrew/bin/nu.",
expect.any(Error),
);
expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin");
});

it("does nothing outside macOS and linux", () => {
const env: NodeJS.ProcessEnv = {
SHELL: "C:/Program Files/Git/bin/bash.exe",
Expand Down
67 changes: 55 additions & 12 deletions apps/desktop/src/syncShellEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,79 @@
import {
listLoginShellCandidates,
mergePathEntries,
readPathFromLaunchctl,
readEnvironmentFromLoginShell,
resolveLoginShell,
ShellEnvironmentReader,
} from "@t3tools/shared/shell";

const LOGIN_SHELL_ENV_NAMES = [
"PATH",
"SSH_AUTH_SOCK",
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
] as const;

function logShellEnvironmentWarning(message: string, error?: unknown): void {
console.warn(`[desktop] ${message}`, error instanceof Error ? error.message : (error ?? ""));
}

export function syncShellEnvironment(
env: NodeJS.ProcessEnv = process.env,
options: {
platform?: NodeJS.Platform;
readEnvironment?: ShellEnvironmentReader;
readLaunchctlPath?: typeof readPathFromLaunchctl;
userShell?: string;
logWarning?: (message: string, error?: unknown) => void;
} = {},
): void {
const platform = options.platform ?? process.platform;
if (platform !== "darwin" && platform !== "linux") return;

try {
const shell = resolveLoginShell(platform, env.SHELL);
if (!shell) return;
const logWarning = options.logWarning ?? logShellEnvironmentWarning;
const readEnvironment = options.readEnvironment ?? readEnvironmentFromLoginShell;
const shellEnvironment: Partial<Record<string, string>> = {};

const shellEnvironment = (options.readEnvironment ?? readEnvironmentFromLoginShell)(shell, [
"PATH",
"SSH_AUTH_SOCK",
]);
try {
for (const shell of listLoginShellCandidates(platform, env.SHELL, options.userShell)) {
try {
Object.assign(shellEnvironment, readEnvironment(shell, LOGIN_SHELL_ENV_NAMES));
if (shellEnvironment.PATH) {
break;
}
} catch (error) {
logWarning(`Failed to read login shell environment from ${shell}.`, error);
}
}

if (shellEnvironment.PATH) {
env.PATH = shellEnvironment.PATH;
const launchctlPath =
platform === "darwin" && !shellEnvironment.PATH
? (options.readLaunchctlPath ?? readPathFromLaunchctl)()
: undefined;
const mergedPath = mergePathEntries(shellEnvironment.PATH ?? launchctlPath, env.PATH, platform);
if (mergedPath) {
env.PATH = mergedPath;
}

if (!env.SSH_AUTH_SOCK && shellEnvironment.SSH_AUTH_SOCK) {
env.SSH_AUTH_SOCK = shellEnvironment.SSH_AUTH_SOCK;
}
} catch {
// Keep inherited environment if shell lookup fails.

for (const name of [
"HOMEBREW_PREFIX",
"HOMEBREW_CELLAR",
"HOMEBREW_REPOSITORY",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
] as const) {
if (!env[name] && shellEnvironment[name]) {
env[name] = shellEnvironment[name];
}
}
} catch (error) {
logWarning("Failed to synchronize the desktop shell environment.", error);
}
}
25 changes: 25 additions & 0 deletions apps/server/src/atomicWrite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Effect, FileSystem, Path } from "effect";
import * as Random from "effect/Random";

export const writeFileStringAtomically = (input: {
readonly filePath: string;
readonly contents: string;
}) =>
Effect.scoped(
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const tempFileId = yield* Random.nextUUIDv4;
const targetDirectory = path.dirname(input.filePath);

yield* fileSystem.makeDirectory(targetDirectory, { recursive: true });
const tempDirectory = yield* fileSystem.makeTempDirectoryScoped({
directory: targetDirectory,
prefix: `${path.basename(input.filePath)}.`,
});
const tempPath = path.join(tempDirectory, `${tempFileId}.tmp`);

yield* fileSystem.writeFileString(tempPath, input.contents);
yield* fileSystem.rename(tempPath, input.filePath);
}),
);
35 changes: 27 additions & 8 deletions apps/server/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { Command } from "effect/unstable/cli";
Expand All @@ -8,11 +6,32 @@ import { NetService } from "@t3tools/shared/Net";
import { cli } from "./cli";
import { version } from "../package.json" with { type: "json" };

const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
const isBunRuntime = typeof Bun !== "undefined";

const main = (Command.run(cli, { version }) as any).pipe(
Effect.scoped,
Effect.provide(CliRuntimeLayer),
);
const main = (Command.run(cli, { version }) as any).pipe(Effect.scoped);

NodeRuntime.runMain(main);
async function bootCli(): Promise<void> {
// Keep runtime selection dynamic so the same source can boot under Bun for
// local development and Node for packaged builds, without forcing the CJS
// build to support top-level await.
if (isBunRuntime) {
const [BunRuntime, BunServices] = await Promise.all([
import("@effect/platform-bun/BunRuntime"),
import("@effect/platform-bun/BunServices"),
]);
const cliRuntimeLayer = Layer.mergeAll(BunServices.layer, NetService.layer);

BunRuntime.runMain(main.pipe(Effect.provide(cliRuntimeLayer)));
return;
}

const [NodeRuntime, NodeServices] = await Promise.all([
import("@effect/platform-node/NodeRuntime"),
import("@effect/platform-node/NodeServices"),
]);
const cliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);

NodeRuntime.runMain(main.pipe(Effect.provide(cliRuntimeLayer)));
}

void bootCli();
35 changes: 35 additions & 0 deletions apps/server/src/checkpointing/Diffs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,39 @@ describe("parseTurnDiffFilesFromUnifiedDiff", () => {
{ path: "a.txt", additions: 2, deletions: 1 },
]);
});

it("parses quoted diff headers for paths that need escaping", () => {
const diff = [
'diff --git "a/src/with space.ts" "b/src/with space.ts"',
"index 1111111..2222222 100644",
'--- "a/src/with space.ts"',
'+++ "b/src/with space.ts"',
"@@ -1 +1 @@",
"-old",
"+new",
"",
].join("\n");

expect(parseTurnDiffFilesFromUnifiedDiff(diff)).toEqual([
{ path: "src/with space.ts", additions: 1, deletions: 1 },
]);
});

it("does not treat added +++ content as a path header", () => {
const diff = [
"diff --git a/a.txt b/a.txt",
"index 1111111..2222222 100644",
"--- a/a.txt",
"+++ b/a.txt",
"@@ -1 +1,2 @@",
" unchanged",
"+hello",
"+++ counter",
"",
].join("\n");

expect(parseTurnDiffFilesFromUnifiedDiff(diff)).toEqual([
{ path: "a.txt", additions: 2, deletions: 0 },
]);
});
});
Loading