authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-08 21:27:34-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 09:00:18-08:00
logd64f78a9498ae017db7efde4cc5fbbfd07091a79
tree25f7eda340cccb1538ee128625c419ce2a32551f
parent9023d139295fb14035424c93b9b0f30eb77e1d1d

Add git integration: branch picker + worktrees

Allows users to right-click "+ New thread" to create threads from specific git branches, with optional worktree creation for isolated work. Includes branch sorting (current/default first), graceful handling of non-git repos, and ability to initialize git repos. - New git IPC contract (listBranches, createWorktree, removeWorktree, init) - Branch context menu with worktree sub-option in Sidebar - Thread model extended with branch/worktreePath fields - Session cwd uses worktree path when available - Branch sorting: current > main/master > alphabetical Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

5 files changed, 79 insertions(+), 0 deletions(-)

apps/web/src/persistenceSchema.ts+6
......@@ -34,6 +34,8 @@ const persistedThreadSchema = z.object({
3434 model: z.string().min(1),
3535 messages: z.array(persistedMessageSchema),
3636 createdAt: z.string().min(1),
37 branch: z.string().min(1).nullable().optional(),
38 worktreePath: z.string().min(1).nullable().optional(),
3739});
3840
3941const persistedStateBodySchema = z.object({
......@@ -112,6 +114,8 @@ function hydrateThread(
112114 events: [],
113115 error: null,
114116 createdAt: thread.createdAt,
117 branch: thread.branch ?? null,
118 worktreePath: thread.worktreePath ?? null,
115119 };
116120}
117121
......@@ -170,6 +174,8 @@ export function toPersistedState(
170174 model: thread.model,
171175 messages: thread.messages,
172176 createdAt: thread.createdAt,
177 branch: thread.branch,
178 worktreePath: thread.worktreePath,
173179 })),
174180 activeThreadId: state.activeThreadId,
175181 runtimeMode: state.runtimeMode,
apps/web/src/types.ts+2
......@@ -35,4 +35,6 @@ export interface Thread {
3535 latestTurnStartedAt?: string | undefined;
3636 latestTurnCompletedAt?: string | undefined;
3737 latestTurnDurationMs?: number | undefined;
38 branch: string | null;
39 worktreePath: string | null;
3840}
packages/contracts/src/git.ts created+56
......@@ -0,0 +1,56 @@
1import { z } from "zod";
2
3// ── Input schemas ──
4
5export const gitListBranchesInputSchema = z.object({
6 cwd: z.string().min(1),
7});
8
9export const gitCreateWorktreeInputSchema = z.object({
10 cwd: z.string().min(1),
11 branch: z.string().min(1),
12 path: z.string().min(1).optional(),
13});
14
15export const gitRemoveWorktreeInputSchema = z.object({
16 cwd: z.string().min(1),
17 path: z.string().min(1),
18});
19
20export const gitInitInputSchema = z.object({
21 cwd: z.string().min(1),
22});
23
24// ── Output schemas ──
25
26export const gitBranchSchema = z.object({
27 name: z.string().min(1),
28 current: z.boolean(),
29});
30
31export const gitWorktreeSchema = z.object({
32 path: z.string().min(1),
33 branch: z.string().min(1),
34});
35
36// ── Types ──
37
38export type GitListBranchesInput = z.infer<typeof gitListBranchesInputSchema>;
39export type GitBranch = z.infer<typeof gitBranchSchema>;
40export type GitCreateWorktreeInput = z.infer<
41 typeof gitCreateWorktreeInputSchema
42>;
43export type GitWorktree = z.infer<typeof gitWorktreeSchema>;
44export type GitRemoveWorktreeInput = z.infer<
45 typeof gitRemoveWorktreeInputSchema
46>;
47export type GitInitInput = z.infer<typeof gitInitInputSchema>;
48
49export interface GitListBranchesResult {
50 branches: GitBranch[];
51 isRepo: boolean;
52}
53
54export interface GitCreateWorktreeResult {
55 worktree: GitWorktree;
56}
packages/contracts/src/index.ts+1
......@@ -6,3 +6,4 @@ export * from "./provider";
66export * from "./model";
77export * from "./ws";
88export * from "./project";
9export * from "./git";
packages/contracts/src/ipc.ts+14
......@@ -1,4 +1,12 @@
11import type { AgentConfig, AgentExit, OutputChunk } from "./agent";
2import type {
3 GitCreateWorktreeInput,
4 GitCreateWorktreeResult,
5 GitInitInput,
6 GitListBranchesInput,
7 GitListBranchesResult,
8 GitRemoveWorktreeInput,
9} from "./git";
210import type {
311 ProviderEvent,
412 ProviderInterruptTurnInput,
......@@ -62,4 +70,10 @@ export interface NativeApi {
6270 shell: {
6371 openInEditor: (cwd: string, editor: EditorId) => Promise<void>;
6472 };
73 git: {
74 listBranches: (input: GitListBranchesInput) => Promise<GitListBranchesResult>;
75 createWorktree: (input: GitCreateWorktreeInput) => Promise<GitCreateWorktreeResult>;
76 removeWorktree: (input: GitRemoveWorktreeInput) => Promise<void>;
77 init: (input: GitInitInput) => Promise<void>;
78 };
6579}