From 4215120d7cc5393f08de1a9d623cd33bbd6abe88 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 10 Apr 2026 09:50:46 +0700 Subject: [PATCH] Fix agent close persistence, whitespace diff filtering, and diff param cleanup - Persist snapshot before emitting closed agent to avoid data loss - Flush after branch rename so state hits disk immediately - Filter phantom whitespace-only files from ignore-whitespace diffs - Only include ignoreWhitespace param when true to keep payloads clean - Add createFeature test helper for AgentFeature construction --- packages/server/src/client/daemon-client.ts | 13 +++++++++---- .../server/src/server/agent/agent-manager.test.ts | 10 ++++++++++ packages/server/src/server/agent/agent-manager.ts | 7 ++++--- .../src/server/agent/agent-metadata-generator.ts | 1 + packages/server/src/utils/checkout-git.ts | 10 ++++++++++ 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/server/src/client/daemon-client.ts b/packages/server/src/client/daemon-client.ts index 6cac9a1c4..aa7695d29 100644 --- a/packages/server/src/client/daemon-client.ts +++ b/packages/server/src/client/daemon-client.ts @@ -2226,15 +2226,20 @@ export class DaemonClient { baseRef?: string; ignoreWhitespace?: boolean; }): { mode: "uncommitted" | "base"; baseRef?: string; ignoreWhitespace?: boolean } { - const ignoreWhitespace = compare.ignoreWhitespace === true; if (compare.mode === "uncommitted") { - return { mode: "uncommitted", ignoreWhitespace }; + return compare.ignoreWhitespace === true + ? { mode: "uncommitted", ignoreWhitespace: true } + : { mode: "uncommitted" }; } const trimmedBaseRef = compare.baseRef?.trim(); if (!trimmedBaseRef) { - return { mode: "base", ignoreWhitespace }; + return compare.ignoreWhitespace === true + ? { mode: "base", ignoreWhitespace: true } + : { mode: "base" }; } - return { mode: "base", baseRef: trimmedBaseRef, ignoreWhitespace }; + return compare.ignoreWhitespace === true + ? { mode: "base", baseRef: trimmedBaseRef, ignoreWhitespace: true } + : { mode: "base", baseRef: trimmedBaseRef }; } async getCheckoutDiff( diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index 76685e899..34c6ba6a0 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -13,6 +13,7 @@ import { AgentManager } from "./agent-manager.js"; import { AgentStorage } from "./agent-storage.js"; import type { AgentClient, + AgentFeature, AgentLaunchContext, AgentPersistenceHandle, AgentRunResult, @@ -47,6 +48,15 @@ const TEST_CAPABILITIES = { supportsToolInvocations: false, } as const; +function createFeature(args: { id: string; label: string; value: boolean }): AgentFeature { + return { + type: "toggle", + id: args.id, + label: args.label, + value: args.value, + }; +} + async function seedWorkspace( database: PaseoDatabaseHandle, options: { directory: string }, diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index e42619143..659e701ec 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -725,7 +725,8 @@ export class AgentManager { const closedAgent = this.prepareAgentForClosure(agent, "agent closed"); await agent.session.close(); this.timelineStore.delete(agentId); - this.emitClosedAgent(closedAgent); + await this.persistSnapshot(closedAgent); + this.emitClosedAgent(closedAgent, { persist: false }); this.logger.trace({ agentId }, "closeAgent: completed"); } @@ -1944,8 +1945,8 @@ export class AgentManager { }; } - private emitClosedAgent(agent: ManagedAgentClosed): void { - this.emitState(agent); + private emitClosedAgent(agent: ManagedAgentClosed, options?: { persist?: boolean }): void { + this.emitState(agent, options); } private subscribeToSession(agent: ActiveManagedAgent): void { if (agent.unsubscribeSession) { diff --git a/packages/server/src/server/agent/agent-metadata-generator.ts b/packages/server/src/server/agent/agent-metadata-generator.ts index 964d6e508..043c14ea9 100644 --- a/packages/server/src/server/agent/agent-metadata-generator.ts +++ b/packages/server/src/server/agent/agent-metadata-generator.ts @@ -233,6 +233,7 @@ export async function generateAndApplyAgentMetadata( try { await renameCurrentBranchImpl(options.cwd, normalizedBranch); options.agentManager.notifyAgentState(options.agentId); + await options.agentManager.flush(); } catch (error) { options.logger.warn( { err: error, agentId: options.agentId, branch: normalizedBranch }, diff --git a/packages/server/src/utils/checkout-git.ts b/packages/server/src/utils/checkout-git.ts index 26245359e..16b1b27cb 100644 --- a/packages/server/src/utils/checkout-git.ts +++ b/packages/server/src/utils/checkout-git.ts @@ -1547,6 +1547,16 @@ export async function getCheckoutDiff( continue; } + // `git diff -w --name-status` can still surface paths whose only changes were + // whitespace. Skip those empty tracked entries so callers don't see phantom files. + if ( + ignoreWhitespace && + change.status.startsWith("M") && + (!stat || (!stat.isBinary && stat.additions === 0 && stat.deletions === 0)) + ) { + continue; + } + structured.push({ path: change.path, isNew: change.isNew,