fix(agent): implement AgentProtocol disposal to prevent memory leaks

This commit is contained in:
Michael Bleigh
2026-05-11 14:56:10 -07:00
parent a7ae31d732
commit aa8eca7bfe
6 changed files with 26 additions and 11 deletions
@@ -193,6 +193,7 @@ export async function runNonInteractive({
let errorToHandle: unknown | undefined;
let scheduler: Scheduler | undefined;
let session: LegacyAgentSession | undefined;
let abortSession = () => {};
try {
consolePatcher.patch();
@@ -296,7 +297,7 @@ export async function runNonInteractive({
}
// Create LegacyAgentSession — owns the agentic loop
const session = new LegacyAgentSession({
session = new LegacyAgentSession({
client: geminiClient,
scheduler,
config,
@@ -305,7 +306,7 @@ export async function runNonInteractive({
// Wire Ctrl+C to session abort
abortSession = () => {
void session.abort();
void session?.abort();
};
abortController.signal.addEventListener('abort', abortSession);
if (abortController.signal.aborted) {
@@ -640,6 +641,7 @@ export async function runNonInteractive({
cleanupStdinCancellation();
abortController.signal.removeEventListener('abort', abortSession);
session?.dispose();
scheduler?.dispose();
consolePatcher.cleanup();
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
+4
View File
@@ -1180,6 +1180,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
[config, getPreferredEditor],
);
useEffect(() => () => {
streamAgent?.dispose?.();
}, [streamAgent]);
const activeStream = streamAgent
? // eslint-disable-next-line react-hooks/rules-of-hooks
useAgentStream({
+4
View File
@@ -34,6 +34,10 @@ export class AgentSession implements AgentProtocol {
return this._protocol.abort();
}
dispose(): void {
this._protocol.dispose?.();
}
get events(): readonly AgentEvent[] {
return this._protocol.events;
}
@@ -71,6 +71,7 @@ export class LegacyAgentProtocol implements AgentProtocol {
private _agentEndEmitted = false;
private _activeStreamId?: string;
private _abortController = new AbortController();
private _disposalController = new AbortController();
private _nextStreamIdOverride?: string;
private _lastToolStatuses = new Map<string, ToolEventStatus>();
@@ -106,10 +107,16 @@ export class LegacyAgentProtocol implements AgentProtocol {
this._config.messageBus.subscribe(
MessageBusType.TOOL_CALLS_UPDATE,
this._handleToolCallsUpdate.bind(this),
{ signal: this._disposalController.signal },
);
}
}
dispose(): void {
this._disposalController.abort();
void this.abort();
}
get events(): readonly AgentEvent[] {
return this._events;
}
+5
View File
@@ -37,6 +37,11 @@ export interface AgentProtocol extends Trajectory {
*/
abort(): Promise<void>;
/**
* Disposes of the protocol, cleaning up any long-lived resources.
*/
dispose?(): void;
/**
* AgentProtocol implements the Trajectory interface and can retrieve existing events.
*/