@@ -2,7 +2,6 @@ import * as fs from "fs";
22import * as path from "path" ;
33import * as os from "os" ;
44import * as crypto from "crypto" ;
5- import { fileURLToPath } from "url" ;
65import matter from "gray-matter" ;
76import ejs from "ejs" ;
87import type { ChatCompletionMessageParam , ChatCompletionContentPart } from "openai/resources/chat/completions" ;
@@ -12,6 +11,7 @@ import { DEEPSEEK_V4_MODELS, supportsMultimodal } from "./common/model-capabilit
1211import {
1312 getCompactPrompt ,
1413 getDefaultSkillPrompt ,
14+ getExtensionRoot ,
1515 getRuntimeContext ,
1616 getSystemPrompt ,
1717 getTools ,
@@ -44,6 +44,8 @@ import {
4444 type PermissionToolCall ,
4545 type UserToolPermission ,
4646} from "./common/permissions" ;
47+ import { clearSessionWorkingDir } from "./tools/bash-handler" ;
48+ import { clearSessionState } from "./common/state" ;
4749
4850export type { PermissionScope } from "./settings" ;
4951export type {
@@ -135,15 +137,6 @@ function accumulateUsagePerModel(
135137 return usagePerModel ;
136138}
137139
138- function getExtensionRoot ( ) : string {
139- if ( typeof __dirname !== "undefined" ) {
140- return path . resolve ( __dirname , ".." ) ;
141- }
142-
143- const currentFilePath = fileURLToPath ( import . meta. url ) ;
144- return path . resolve ( path . dirname ( currentFilePath ) , ".." ) ;
145- }
146-
147140function getTotalTokens ( usage : ModelUsage | null | undefined ) : number {
148141 if ( ! isUsageRecord ( usage ) ) {
149142 return 0 ;
@@ -346,6 +339,18 @@ export class SessionManager {
346339 }
347340
348341 dispose ( ) : void {
342+ const controller = this . activePromptController ;
343+ if ( controller && ! controller . signal . aborted ) {
344+ controller . abort ( ) ;
345+ }
346+ this . activePromptController = null ;
347+ for ( const sessionController of this . sessionControllers . values ( ) ) {
348+ if ( ! sessionController . signal . aborted ) {
349+ sessionController . abort ( ) ;
350+ }
351+ }
352+ this . sessionControllers . clear ( ) ;
353+ this . processTimeoutControls . clear ( ) ;
349354 this . mcpManager . disconnect ( ) ;
350355 }
351356
@@ -979,7 +984,12 @@ The candidate skills are as follows:\n\n`;
979984 const droppedEntries = sortedEntries . filter ( ( item ) => ! keptIds . has ( item . id ) ) ;
980985 index . entries = keptEntries ;
981986 this . saveSessionsIndex ( index ) ;
982- this . removeSessionMessages ( droppedEntries . map ( ( item ) => item . id ) ) ;
987+ for ( const dropped of droppedEntries ) {
988+ this . cleanupSessionResources ( dropped . id , {
989+ removeMessages : true ,
990+ processIds : this . getProcessIds ( dropped . processes ?? null ) ,
991+ } ) ;
992+ }
983993
984994 const promptToolOptions = this . getPromptToolOptions ( ) ;
985995 const systemPrompt = getSystemPrompt ( this . projectRoot , promptToolOptions ) ;
@@ -1590,23 +1600,25 @@ ${skillMd}
15901600
15911601 /**
15921602 * Delete a session by its ID.
1593- * Removes the session entry from the index and deletes the associated messages file.
1603+ * Removes the session entry from the index and cleans up associated resources
1604+ * such as message files, in-memory state caches, working directory state,
1605+ * session controllers, and tracked process timeout controls.
15941606 * Returns true if the session was found and deleted, false otherwise.
15951607 */
15961608 deleteSession ( sessionId : string ) : boolean {
15971609 const index = this . loadSessionsIndex ( ) ;
1598- const entryIndex = index . entries . findIndex ( ( entry ) => entry . id === sessionId ) ;
1599- if ( entryIndex === - 1 ) {
1610+ const targetEntry = index . entries . find ( ( entry ) => entry . id === sessionId ) ?? null ;
1611+ const nextEntries = index . entries . filter ( ( entry ) => entry . id !== sessionId ) ;
1612+ if ( nextEntries . length === index . entries . length ) {
16001613 return false ;
16011614 }
16021615
1603- // Remove from index
1604- index . entries . splice ( entryIndex , 1 ) ;
1616+ index . entries = nextEntries ;
16051617 this . saveSessionsIndex ( index ) ;
1606-
1607- // Remove messages file
1608- this . removeSessionMessages ( [ sessionId ] ) ;
1609-
1618+ this . cleanupSessionResources ( sessionId , {
1619+ removeMessages : true ,
1620+ processIds : this . getProcessIds ( targetEntry ?. processes ?? null ) ,
1621+ } ) ;
16101622 return true ;
16111623 }
16121624
@@ -1853,6 +1865,42 @@ ${skillMd}
18531865 }
18541866 }
18551867
1868+ private cleanupSessionResources (
1869+ sessionId : string ,
1870+ options : { removeMessages : boolean ; processIds ?: number [ ] }
1871+ ) : void {
1872+ const processIds = options . processIds ?? [ ] ;
1873+ for ( const pid of processIds ) {
1874+ const processControlKey = this . getProcessControlKey ( sessionId , pid ) ;
1875+ if ( ! this . processTimeoutControls . has ( processControlKey ) ) {
1876+ continue ;
1877+ }
1878+
1879+ const killedGroup = killProcessTree ( pid , "SIGKILL" ) ;
1880+ if ( killedGroup ) {
1881+ this . processTimeoutControls . delete ( processControlKey ) ;
1882+ continue ;
1883+ }
1884+ try {
1885+ process . kill ( pid , "SIGKILL" ) ;
1886+ } catch {
1887+ // ignore process-kill failures during cleanup
1888+ }
1889+ this . processTimeoutControls . delete ( processControlKey ) ;
1890+ }
1891+
1892+ clearSessionState ( sessionId ) ;
1893+ clearSessionWorkingDir ( sessionId ) ;
1894+ const controller = this . sessionControllers . get ( sessionId ) ;
1895+ if ( controller && ! controller . signal . aborted ) {
1896+ controller . abort ( ) ;
1897+ }
1898+ this . sessionControllers . delete ( sessionId ) ;
1899+ if ( options . removeMessages ) {
1900+ this . removeSessionMessages ( [ sessionId ] ) ;
1901+ }
1902+ }
1903+
18561904 private appendSessionMessage ( sessionId : string , message : SessionMessage ) : void {
18571905 this . ensureProjectDir ( ) ;
18581906 const messagePath = this . getSessionMessagesPath ( sessionId ) ;
0 commit comments