fix(coding-agent): prevent duplicate messages on startup session switch (#7110)

This commit is contained in:
Michael Yu 2026-07-27 17:59:27 +08:00 • committed by GitHub
parent 61da9e2f37
commit c2275d6703
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 3 deletions

View file

@ -1730,17 +1730,27 @@ export class InteractiveMode {
}
private async rebindCurrentSession(options: { renderBeforeBind?: boolean } = {}): Promise<void> {
const session = this.session;
this.unsubscribe?.();
this.unsubscribe = undefined;
this.applyRuntimeSettings();
if (options.renderBeforeBind) {
this.renderCurrentSessionState();
this.subscribeToAgent();
await this.bindCurrentSessionExtensions();
} else {
await this.bindCurrentSessionExtensions();
}
await this.bindCurrentSessionExtensions();
if (this.session !== session) {
return;
}
if (!options.renderBeforeBind) {
this.subscribeToAgent();
}
await this.updateAvailableProviderCount();
this.updateEditorBorderColor();
this.updateTerminalTitle();

View file

@ -0,0 +1,77 @@
import { describe, expect, it, vi } from "vitest";
import { InteractiveMode } from "../../../src/modes/interactive/interactive-mode.ts";
type RebindContext = {
session: object;
unsubscribe?: () => void;
applyRuntimeSettings: () => void;
renderCurrentSessionState: () => void;
bindCurrentSessionExtensions: () => Promise<void>;
subscribeToAgent: () => void;
updateAvailableProviderCount: () => Promise<void>;
updateEditorBorderColor: () => void;
updateTerminalTitle: () => void;
};
type InteractiveModePrototype = {
rebindCurrentSession(this: RebindContext, options?: { renderBeforeBind?: boolean }): Promise<void>;
};
const interactiveModePrototype = InteractiveMode.prototype as unknown as InteractiveModePrototype;
describe("overlapping startup and replacement session rebinds", () => {
it("does not subscribe from the stale startup rebind", async () => {
const startupSession = {};
const replacementSession = {};
let resolveStartupBind!: () => void;
let resolveReplacementBind!: () => void;
const startupBind = new Promise<void>((resolve) => {
resolveStartupBind = resolve;
});
const replacementBind = new Promise<void>((resolve) => {
resolveReplacementBind = resolve;
});
const subscribeToAgent = vi.fn();
const updateTerminalTitle = vi.fn();
let bindCount = 0;
const context: RebindContext = {
session: startupSession,
applyRuntimeSettings: () => {},
renderCurrentSessionState: () => {},
bindCurrentSessionExtensions: () => {
bindCount += 1;
return bindCount === 1 ? startupBind : replacementBind;
},
subscribeToAgent,
updateAvailableProviderCount: async () => {},
updateEditorBorderColor: () => {},
updateTerminalTitle,
};
const startupRebind = interactiveModePrototype.rebindCurrentSession.call(context);
expect(bindCount).toBe(1);
context.session = replacementSession;
const replacementRebind = interactiveModePrototype.rebindCurrentSession.call(context, {
renderBeforeBind: true,
});
expect(bindCount).toBe(2);
expect(subscribeToAgent).toHaveBeenCalledTimes(1);
resolveStartupBind();
await startupRebind;
expect(subscribeToAgent).toHaveBeenCalledTimes(1);
expect(updateTerminalTitle).not.toHaveBeenCalled();
resolveReplacementBind();
await replacementRebind;
expect(subscribeToAgent).toHaveBeenCalledTimes(1);
expect(updateTerminalTitle).toHaveBeenCalledTimes(1);
});
});