Skip to content

Commit 65c1f21

Browse files
feat: full git management — fetch, pull, force push, stash, branch delete/merge, create PR, fix remote checkout
1 parent 6757f5e commit 65c1f21

7 files changed

Lines changed: 709 additions & 57 deletions

File tree

crates/tauri-app/agent-sidecar/index.mjs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ let approvalCounter = 0;
5555
let hasCompletedQuery = false;
5656
let lastSessionId = null;
5757

58+
// Incremental streaming state — the SDK yields full message snapshots,
59+
// so we diff against the previous lengths to emit only new characters.
60+
let lastTextLen = 0;
61+
let lastThinkingLen = 0;
62+
5863
// ── Stdin reader ─────────────────────────────────────────────────────────────
5964

6065
const rl = createInterface({ input: process.stdin, terminal: false });
@@ -187,6 +192,10 @@ async function handleQuery(cmd) {
187192
let capturedSessionId = sessionId || null;
188193
let turnEmitted = false;
189194

195+
// Reset incremental streaming counters for new query.
196+
lastTextLen = 0;
197+
lastThinkingLen = 0;
198+
190199
// Emit turn_started so the frontend shows "generating" state
191200
emit({ type: "turn_started" });
192201

@@ -250,6 +259,8 @@ async function handleQuery(cmd) {
250259

251260
hasCompletedQuery = true;
252261
lastSessionId = capturedSessionId;
262+
lastTextLen = 0;
263+
lastThinkingLen = 0;
253264
emit({ type: "turn_completed", sessionId: capturedSessionId || "" });
254265
turnEmitted = true;
255266
continue;
@@ -264,11 +275,13 @@ async function handleQuery(cmd) {
264275
continue;
265276
}
266277

267-
// assistant message — extract content blocks
278+
// assistant message — extract content blocks (incremental diff)
268279
if (msgType === "assistant" && message.message?.content) {
269280
for (const block of message.message.content) {
270281
if (block.type === "text" && block.text) {
271-
emit({ type: "text_delta", text: block.text });
282+
const newText = block.text.slice(lastTextLen);
283+
if (newText) emit({ type: "text_delta", text: newText });
284+
lastTextLen = block.text.length;
272285
} else if (block.type === "tool_use") {
273286
emit({
274287
type: "tool_use_start",
@@ -283,7 +296,9 @@ async function handleQuery(cmd) {
283296
});
284297
}
285298
} else if (block.type === "thinking" && block.thinking) {
286-
emit({ type: "thinking_delta", text: block.thinking });
299+
const newThinking = block.thinking.slice(lastThinkingLen);
300+
if (newThinking) emit({ type: "thinking_delta", text: newThinking });
301+
lastThinkingLen = block.thinking.length;
287302
}
288303
}
289304
continue;

crates/tauri-app/frontend/e2e/helpers.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,26 @@ export async function injectMockIPC(page: Page) {
280280
case "list_slash_commands":
281281
return [{ name: "/help", description: "Show help", source: "built-in" }];
282282

283+
// Git extended
284+
case "git_fetch":
285+
return "Fetched successfully";
286+
case "git_pull":
287+
return "Already up to date.";
288+
case "git_push_force":
289+
return "Force pushed successfully";
290+
case "git_delete_branch":
291+
return `Deleted branch ${args.name}`;
292+
case "git_merge_branch":
293+
return `Merged ${args.branch}`;
294+
case "git_stash":
295+
return "Saved working directory";
296+
case "git_stash_pop":
297+
return "Applied stash";
298+
case "git_create_pr":
299+
return `https://github.com/test/repo/pull/1`;
300+
case "git_diff_branches":
301+
return [];
302+
283303
// Diff
284304
case "get_changed_files":
285305
case "get_session_diff":

crates/tauri-app/frontend/src/components/chat/ThreadSetup.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function ThreadSetup(props: Props) {
2222
async function loadBranches() {
2323
setLoadingBranches(true);
2424
try {
25+
// Fetch first so remote-only branches are available locally
26+
await ipc.gitFetch(props.repoPath).catch(() => {});
2527
const list = await ipc.gitBranches(props.repoPath);
2628
setBranches(list);
2729
} catch (e) {
@@ -39,6 +41,8 @@ export function ThreadSetup(props: Props) {
3941
const thread = store.projects.flatMap((p) => p.threads).find((t) => t.id === props.threadId);
4042
const wt = await ipc.createWorktree(props.threadId, thread?.title || "worktree", props.repoPath);
4143
setStore("worktrees", props.threadId, wt);
44+
// Fetch so remote-only branches can be checked out
45+
await ipc.gitFetch(wt.path).catch(() => {});
4246
// Checkout the requested branch in the worktree
4347
if (branchName !== "main" && branchName !== "master") {
4448
await ipc.gitCheckout(wt.path, branchName);

0 commit comments

Comments
 (0)