Skip to content

Commit 882f4de

Browse files
authored
fix: Preserve session state on idle-kill instead of destroying it (#1459)
1 parent 46c330b commit 882f4de

File tree

3 files changed

+50
-3
lines changed

3 files changed

+50
-3
lines changed

apps/code/src/main/services/agent/service.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ describe("AgentService", () => {
285285
lastActivityAt: Date.now(),
286286
config: {},
287287
promptPending: false,
288+
inFlightMcpToolCalls: new Map(),
288289
...overrides,
289290
});
290291
}
@@ -375,6 +376,34 @@ describe("AgentService", () => {
375376
expect(getIdleTimeouts(service).has("run-1")).toBe(true);
376377
});
377378

379+
it("reschedules when inFlightMcpToolCalls is non-empty at timeout", () => {
380+
const toolCalls = new Map([["tool-1", "some-mcp-tool"]]);
381+
injectSession(service, "run-1", { inFlightMcpToolCalls: toolCalls });
382+
service.recordActivity("run-1");
383+
384+
vi.advanceTimersByTime(15 * 60 * 1000);
385+
386+
expect(service.emit).not.toHaveBeenCalledWith(
387+
"session-idle-killed",
388+
expect.anything(),
389+
);
390+
expect(getIdleTimeouts(service).has("run-1")).toBe(true);
391+
});
392+
393+
it("kills session when inFlightMcpToolCalls is empty", () => {
394+
injectSession(service, "run-1", {
395+
inFlightMcpToolCalls: new Map(),
396+
});
397+
service.recordActivity("run-1");
398+
399+
vi.advanceTimersByTime(15 * 60 * 1000);
400+
401+
expect(service.emit).toHaveBeenCalledWith(
402+
"session-idle-killed",
403+
expect.objectContaining({ taskRunId: "run-1" }),
404+
);
405+
});
406+
378407
it("checkIdleDeadlines kills expired sessions on resume", () => {
379408
injectSession(service, "run-1");
380409
service.recordActivity("run-1");

apps/code/src/main/services/agent/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
365365
*/
366366
public hasActiveSessions(): boolean {
367367
for (const session of this.sessions.values()) {
368-
if (session.promptPending) {
368+
if (session.promptPending || session.inFlightMcpToolCalls.size > 0) {
369369
log.info("Active session found", { sessionId: session.taskRunId });
370370
return true;
371371
}
@@ -391,7 +391,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
391391
private killIdleSession(taskRunId: string): void {
392392
const session = this.sessions.get(taskRunId);
393393
if (!session) return;
394-
if (session.promptPending) {
394+
if (session.promptPending || session.inFlightMcpToolCalls.size > 0) {
395395
this.recordActivity(taskRunId);
396396
return;
397397
}

apps/code/src/renderer/features/sessions/service/service.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export class SessionService {
127127
onData: (event: { taskRunId: string }) => {
128128
const { taskRunId } = event;
129129
log.info("Session idle-killed by main process", { taskRunId });
130-
this.teardownSession(taskRunId);
130+
this.handleIdleKill(taskRunId);
131131
},
132132
onError: (err: unknown) => {
133133
log.debug("Idle-killed subscription error", { error: err });
@@ -481,6 +481,24 @@ export class SessionService {
481481
removePersistedConfigOptions(taskRunId);
482482
}
483483

484+
/**
485+
* Handle an idle-kill from the main process without destroying session state.
486+
* The main process already cleaned up the agent, so we only need to
487+
* unsubscribe from the channel and mark the session as errored.
488+
* Preserves events, logUrl, configOptions and adapter so that Retry
489+
* can reconnect with full context via unstable_resumeSession.
490+
*/
491+
private handleIdleKill(taskRunId: string): void {
492+
this.unsubscribeFromChannel(taskRunId);
493+
sessionStoreSetters.updateSession(taskRunId, {
494+
status: "error",
495+
errorMessage:
496+
"Session disconnected due to inactivity. Click Retry to reconnect.",
497+
isPromptPending: false,
498+
promptStartedAt: null,
499+
});
500+
}
501+
484502
private setErrorSession(
485503
taskId: string,
486504
taskRunId: string,

0 commit comments

Comments
 (0)