feat: add diff stats and archive actions to workspace sidebar

This commit is contained in:
Mohamed Boudra
2026-03-09 16:38:36 +07:00
parent 7f11b93e0f
commit a15b52efc8
12 changed files with 304 additions and 170 deletions
@@ -1415,6 +1415,16 @@ export class ClaudeAgentClient implements AgentClient {
} catch {
this.claudePath = null;
}
if (this.claudePath) {
try {
const version = execSync(`${this.claudePath} --version`, { encoding: "utf8" }).trim();
this.logger.info({ claudePath: this.claudePath, version }, "Resolved Claude binary");
} catch {
this.logger.info({ claudePath: this.claudePath }, "Resolved Claude binary (version unknown)");
}
} else {
this.logger.warn("Claude binary not found via 'which claude'; SDK will use bundled binary");
}
}
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
+9
View File
@@ -137,6 +137,7 @@ import {
import { createAgentWorktree, runAsyncWorktreeBootstrap } from './worktree-bootstrap.js'
import {
getCheckoutDiff,
getCheckoutShortstat,
getCheckoutStatus,
listBranchSuggestions,
NotGitRepoError,
@@ -5253,6 +5254,13 @@ export class Session {
// Fall back to the persisted label if checkout metadata is unavailable.
}
let diffStat: { additions: number; deletions: number } | null = null
try {
diffStat = await getCheckoutShortstat(workspace.cwd)
} catch {
// Non-critical — leave null on failure.
}
return {
id: workspace.workspaceId,
projectId: workspace.projectId,
@@ -5263,6 +5271,7 @@ export class Session {
name: displayName,
status: 'done',
activityAt: null,
diffStat,
}
}
+4
View File
@@ -1490,6 +1490,10 @@ export const WorkspaceDescriptorPayloadSchema = z.object({
name: z.string(),
status: WorkspaceStateBucketSchema,
activityAt: z.string().nullable(),
diffStat: z.object({
additions: z.number(),
deletions: z.number(),
}).nullable().optional(),
})
export const AgentUpdateMessageSchema = z.object({
+71
View File
@@ -1087,6 +1087,77 @@ export async function getCheckoutStatusLite(
};
}
export interface CheckoutShortstat {
additions: number;
deletions: number;
}
export async function getCheckoutShortstat(
cwd: string,
context?: CheckoutContext
): Promise<CheckoutShortstat | null> {
try {
await requireGitRepo(cwd);
} catch {
return null;
}
const configured = await getConfiguredBaseRefForCwd(cwd, context);
const baseRef = configured.baseRef ?? (await resolveBaseRef(cwd));
if (!baseRef) {
return null;
}
const currentBranch = await getCurrentBranch(cwd);
if (currentBranch === baseRef) {
return null;
}
let mergeBase: string;
try {
const { stdout } = await execAsync(`git merge-base HEAD ${baseRef}`, {
cwd,
env: READ_ONLY_GIT_ENV,
});
mergeBase = stdout.trim();
if (!mergeBase) {
return null;
}
} catch {
return null;
}
try {
const { stdout } = await execAsync(`git diff --shortstat ${mergeBase}`, {
cwd,
env: READ_ONLY_GIT_ENV,
});
const text = stdout.trim();
if (!text) {
return null;
}
let additions = 0;
let deletions = 0;
const addMatch = text.match(/(\d+)\s+insertion/);
if (addMatch) {
additions = Number.parseInt(addMatch[1]!, 10);
}
const delMatch = text.match(/(\d+)\s+deletion/);
if (delMatch) {
deletions = Number.parseInt(delMatch[1]!, 10);
}
if (additions === 0 && deletions === 0) {
return null;
}
return { additions, deletions };
} catch {
return null;
}
}
export async function getCheckoutDiff(
cwd: string,
compare: CheckoutDiffCompare,