FN-5752: flush merger agent logs before disposing sessions

Ensure merger AI activity is persisted to task logs before sessions are torn down.

- Flush AgentLogger output in merger AI-session finally blocks for autostash conflict resolution, autostash hard-fail recovery, and rebase conflict resolution paths
- Guard flush/dispose cleanup with safe try/catch handling to avoid teardown-time logging loss
- Add a regression test asserting logger flush occurs before session disposal during AI rebase conflict resolution
- Export the rebase conflict resolver helper for targeted test coverage
- Add a patch changeset for @runfusion/fusion documenting the agent-log visibility fix

Files changed:
 .changeset/fn-5752-merger-log-flush.md             |  5 ++++
 packages/engine/src/__tests__/merger-conflict-resolution.test.ts   | 30 ++++++++++++++++++++++
 packages/engine/src/merger.ts                      | 22 +++++++++++++++-
 3 files changed, 56 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-5752

Fusion-Task-Lineage: 3c6023f0-cc9b-47e1-bfd4-69b40e011d84
This commit is contained in:
gsxdsm
2026-05-30 15:06:04 -07:00
parent 1594470144
commit 292bf07385
3 changed files with 56 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Fix merger agent-log visibility by flushing buffered `AgentLogger` output before disposing AI sessions used for autostash conflict resolution, autostash hard-fail recovery, and rebase conflict resolution. This ensures trailing text/thinking deltas are persisted so merger activity reliably appears in the task agent log panel.

View File

@@ -145,8 +145,10 @@ import {
resolveTaskDiffBaseRef, resolveTaskDiffBaseRef,
commitOrAmendMergeWithFixes, commitOrAmendMergeWithFixes,
MergeAbortedError, MergeAbortedError,
__testOnlyResolveComplexRebaseConflictsWithAi,
type ConflictCategory, type ConflictCategory,
} from "../merger.js"; } from "../merger.js";
import { AgentLogger } from "../agent-logger.js";
import { mergerLog } from "../logger.js"; import { mergerLog } from "../logger.js";
import { createFnAgent } from "../pi.js"; import { createFnAgent } from "../pi.js";
import { execSync, exec } from "node:child_process"; import { execSync, exec } from "node:child_process";
@@ -266,6 +268,34 @@ function setupFailingFallbackStrategy() {
/** @deprecated Renamed to setupFailingFallbackStrategy. */ /** @deprecated Renamed to setupFailingFallbackStrategy. */
const setupFailingTheirsStrategy = setupFailingFallbackStrategy; const setupFailingTheirsStrategy = setupFailingFallbackStrategy;
describe("merger agent logger flushing", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("flushes buffered rebase conflict agent logs before disposing session", async () => {
const session = {
prompt: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn(),
getSessionStats: vi.fn().mockReturnValue(undefined),
};
mockedCreateFnAgent.mockResolvedValue({ session } as any);
const flushSpy = vi.spyOn(AgentLogger.prototype, "flush").mockResolvedValue(undefined);
await __testOnlyResolveComplexRebaseConflictsWithAi(
createMockStore(),
"/tmp/root",
"FN-5752",
{ ...DEFAULT_SETTINGS },
["src/conflicted.ts"],
);
expect(flushSpy).toHaveBeenCalledTimes(1);
expect(session.dispose).toHaveBeenCalledTimes(1);
expect(flushSpy.mock.invocationCallOrder[0]).toBeLessThan(session.dispose.mock.invocationCallOrder[0]);
});
});
describe("detectResolvableConflicts", () => { describe("detectResolvableConflicts", () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -3054,6 +3054,11 @@ ${fileList}
await store.logEntry(taskId, "Autostash conflict agent encountered an error", msg); await store.logEntry(taskId, "Autostash conflict agent encountered an error", msg);
return { success: false, error: msg }; return { success: false, error: msg };
} finally { } finally {
try {
await agentLogger.flush();
} catch {
// ignore
}
try { try {
session.dispose(); session.dispose();
} catch { } catch {
@@ -3465,6 +3470,11 @@ ${fileList}
await store.logEntry(taskId, "Autostash hard-fail recovery agent encountered an error", msg); await store.logEntry(taskId, "Autostash hard-fail recovery agent encountered an error", msg);
return { success: false, error: msg }; return { success: false, error: msg };
} finally { } finally {
try {
await agentLogger.flush();
} catch {
// ignore
}
try { try {
session.dispose(); session.dispose();
} catch { } catch {
@@ -3983,6 +3993,7 @@ async function buildDeterministicMergeMessage(params: {
} }
export { buildDeterministicMergeMessage as __testOnlyBuildDeterministicMergeMessage }; export { buildDeterministicMergeMessage as __testOnlyBuildDeterministicMergeMessage };
export { resolveComplexRebaseConflictsWithAi as __testOnlyResolveComplexRebaseConflictsWithAi };
/** /**
* Stage current changes and either: * Stage current changes and either:
@@ -6826,7 +6837,16 @@ You are assisting with a paused \`git pull --rebase\`.
}); });
await accumulateSessionTokenUsage(store, taskId, session); await accumulateSessionTokenUsage(store, taskId, session);
} finally { } finally {
session.dispose(); try {
await agentLogger.flush();
} catch {
// ignore
}
try {
session.dispose();
} catch {
// ignore
}
} }
} }