@@ -17,7 +17,12 @@ import {
1717 getTools ,
1818 type ToolDefinition ,
1919} from "./prompt" ;
20- import { ToolExecutor , type CreateOpenAIClient } from "./tools/executor" ;
20+ import {
21+ ToolExecutor ,
22+ type CreateOpenAIClient ,
23+ type ProcessTimeoutControl ,
24+ type ProcessTimeoutInfo ,
25+ } from "./tools/executor" ;
2126import { McpManager } from "./mcp/mcp-manager" ;
2227import type { McpServerConfig } from "./settings" ;
2328import { logApiError } from "./common/error-logger" ;
@@ -134,6 +139,21 @@ export type ModelUsage = {
134139 total_reqs ?: number ;
135140} ;
136141
142+ export type SessionProcessEntry = {
143+ startTime : string ;
144+ command : string ;
145+ timeoutMs ?: number ;
146+ deadlineAt ?: string ;
147+ timedOut ?: boolean ;
148+ } ;
149+
150+ export type BashTimeoutAdjustment = {
151+ processId : string ;
152+ timeoutMs : number ;
153+ deadlineAt : string ;
154+ timedOut : boolean ;
155+ } ;
156+
137157export type SessionEntry = {
138158 id : string ;
139159 summary : string | null ;
@@ -148,7 +168,7 @@ export type SessionEntry = {
148168 activeTokens : number ;
149169 createTime : string ;
150170 updateTime : string ;
151- processes : Map < string , { startTime : string ; command : string } > | null ; // {pid: {startTime, command} }
171+ processes : Map < string , SessionProcessEntry > | null ; // {pid: process info }
152172} ;
153173
154174export type SessionsIndex = {
@@ -234,6 +254,7 @@ export class SessionManager {
234254 private activeSessionId : string | null = null ;
235255 private activePromptController : AbortController | null = null ;
236256 private readonly sessionControllers = new Map < string , AbortController > ( ) ;
257+ private readonly processTimeoutControls = new Map < string , ProcessTimeoutControl > ( ) ;
237258 private readonly toolExecutor : ToolExecutor ;
238259 private readonly mcpManager = new McpManager ( ) ;
239260 private mcpToolDefinitions : ToolDefinition [ ] = [ ] ;
@@ -1360,6 +1381,7 @@ ${skillMd}
13601381 const killedPids : number [ ] = [ ] ;
13611382 const failedPids : number [ ] = [ ] ;
13621383 for ( const pid of processIds ) {
1384+ this . processTimeoutControls . delete ( this . getProcessControlKey ( sessionId , pid ) ) ;
13631385 if ( killProcessTree ( pid , "SIGKILL" ) ) {
13641386 killedPids . push ( pid ) ;
13651387 continue ;
@@ -1397,6 +1419,37 @@ ${skillMd}
13971419 return ! this . sessionControllers . has ( sessionId ) ;
13981420 }
13991421
1422+ adjustActiveBashTimeout ( deltaMs : number ) : BashTimeoutAdjustment | null {
1423+ const sessionId = this . activeSessionId ;
1424+ if ( ! sessionId || ! Number . isFinite ( deltaMs ) ) {
1425+ return null ;
1426+ }
1427+ const session = this . getSession ( sessionId ) ;
1428+ if ( ! session ?. processes ) {
1429+ return null ;
1430+ }
1431+
1432+ let selectedPid : string | null = null ;
1433+ for ( const pid of session . processes . keys ( ) ) {
1434+ if ( this . processTimeoutControls . has ( this . getProcessControlKey ( sessionId , pid ) ) ) {
1435+ selectedPid = pid ;
1436+ }
1437+ }
1438+ if ( ! selectedPid ) {
1439+ return null ;
1440+ }
1441+
1442+ const control = this . processTimeoutControls . get ( this . getProcessControlKey ( sessionId , selectedPid ) ) ;
1443+ if ( ! control ) {
1444+ return null ;
1445+ }
1446+
1447+ const current = control . getInfo ( ) ;
1448+ const next = control . setTimeoutMs ( current . timeoutMs + deltaMs ) ;
1449+ this . updateSessionProcessTimeout ( sessionId , selectedPid , next ) ;
1450+ return this . buildBashTimeoutAdjustment ( selectedPid , next ) ;
1451+ }
1452+
14001453 listSessions ( ) : SessionEntry [ ] {
14011454 const index = this . loadSessionsIndex ( ) ;
14021455 return index . entries ;
@@ -1741,6 +1794,7 @@ ${skillMd}
17411794 onProcessStart : ( pid , command ) => this . addSessionProcess ( sessionId , pid , command ) ,
17421795 onProcessExit : ( pid ) => this . removeSessionProcess ( sessionId , pid ) ,
17431796 onProcessStdout : ( pid , chunk ) => this . onProcessStdout ?.( Number ( pid ) , chunk ) ,
1797+ onProcessTimeoutControl : ( pid , control ) => this . setSessionProcessTimeoutControl ( sessionId , pid , control ) ,
17441798 shouldStop : ( ) => this . isInterrupted ( sessionId ) ,
17451799 } ) ;
17461800 if ( this . isInterrupted ( sessionId ) ) {
@@ -2137,6 +2191,7 @@ ${skillMd}
21372191
21382192 private removeSessionProcess ( sessionId : string , processId : string | number ) : void {
21392193 const now = new Date ( ) . toISOString ( ) ;
2194+ this . processTimeoutControls . delete ( this . getProcessControlKey ( sessionId , processId ) ) ;
21402195 this . updateSessionEntry ( sessionId , ( entry ) => {
21412196 const processes = new Map ( entry . processes ?? [ ] ) ;
21422197 processes . delete ( String ( processId ) ) ;
@@ -2148,7 +2203,58 @@ ${skillMd}
21482203 } ) ;
21492204 }
21502205
2151- private getProcessIds ( processes : Map < string , { startTime : string ; command : string } > | null ) : number [ ] {
2206+ private setSessionProcessTimeoutControl (
2207+ sessionId : string ,
2208+ processId : string | number ,
2209+ control : ProcessTimeoutControl | null
2210+ ) : void {
2211+ const key = this . getProcessControlKey ( sessionId , processId ) ;
2212+ if ( ! control ) {
2213+ this . processTimeoutControls . delete ( key ) ;
2214+ return ;
2215+ }
2216+
2217+ this . processTimeoutControls . set ( key , control ) ;
2218+ this . updateSessionProcessTimeout ( sessionId , processId , control . getInfo ( ) ) ;
2219+ }
2220+
2221+ private updateSessionProcessTimeout ( sessionId : string , processId : string | number , info : ProcessTimeoutInfo ) : void {
2222+ const now = new Date ( ) . toISOString ( ) ;
2223+ this . updateSessionEntry ( sessionId , ( entry ) => {
2224+ const processes = new Map ( entry . processes ?? [ ] ) ;
2225+ const pid = String ( processId ) ;
2226+ const processInfo = processes . get ( pid ) ;
2227+ if ( ! processInfo ) {
2228+ return entry ;
2229+ }
2230+ processes . set ( pid , {
2231+ ...processInfo ,
2232+ timeoutMs : info . timeoutMs ,
2233+ deadlineAt : new Date ( info . deadlineAtMs ) . toISOString ( ) ,
2234+ timedOut : info . timedOut ,
2235+ } ) ;
2236+ return {
2237+ ...entry ,
2238+ processes,
2239+ updateTime : now ,
2240+ } ;
2241+ } ) ;
2242+ }
2243+
2244+ private buildBashTimeoutAdjustment ( processId : string , info : ProcessTimeoutInfo ) : BashTimeoutAdjustment {
2245+ return {
2246+ processId,
2247+ timeoutMs : info . timeoutMs ,
2248+ deadlineAt : new Date ( info . deadlineAtMs ) . toISOString ( ) ,
2249+ timedOut : info . timedOut ,
2250+ } ;
2251+ }
2252+
2253+ private getProcessControlKey ( sessionId : string , processId : string | number ) : string {
2254+ return `${ sessionId } :${ String ( processId ) } ` ;
2255+ }
2256+
2257+ private getProcessIds ( processes : Map < string , SessionProcessEntry > | null ) : number [ ] {
21522258 if ( ! processes ) {
21532259 return [ ] ;
21542260 }
@@ -2232,11 +2338,11 @@ ${skillMd}
22322338 return usagePerModel ;
22332339 }
22342340
2235- private deserializeProcesses ( value : unknown ) : Map < string , { startTime : string ; command : string } > | null {
2341+ private deserializeProcesses ( value : unknown ) : Map < string , SessionProcessEntry > | null {
22362342 if ( ! value || typeof value !== "object" ) {
22372343 return null ;
22382344 }
2239- const processes = new Map < string , { startTime : string ; command : string } > ( ) ;
2345+ const processes = new Map < string , SessionProcessEntry > ( ) ;
22402346 for ( const [ pid , entry ] of Object . entries ( value as Record < string , unknown > ) ) {
22412347 if ( ! pid ) {
22422348 continue ;
@@ -2245,22 +2351,34 @@ ${skillMd}
22452351 // Backward compatibility for old format where just stored start time
22462352 processes . set ( pid , { startTime : entry , command : "Running process..." } ) ;
22472353 } else if ( typeof entry === "object" && entry !== null ) {
2248- const obj = entry as { startTime ?: unknown ; command ?: unknown } ;
2354+ const obj = entry as {
2355+ startTime ?: unknown ;
2356+ command ?: unknown ;
2357+ timeoutMs ?: unknown ;
2358+ deadlineAt ?: unknown ;
2359+ timedOut ?: unknown ;
2360+ } ;
22492361 const startTime = typeof obj . startTime === "string" ? obj . startTime : new Date ( ) . toISOString ( ) ;
22502362 const command = typeof obj . command === "string" ? obj . command : "Running process..." ;
2251- processes . set ( pid , { startTime, command } ) ;
2363+ processes . set ( pid , {
2364+ startTime,
2365+ command,
2366+ timeoutMs : typeof obj . timeoutMs === "number" ? obj . timeoutMs : undefined ,
2367+ deadlineAt : typeof obj . deadlineAt === "string" ? obj . deadlineAt : undefined ,
2368+ timedOut : typeof obj . timedOut === "boolean" ? obj . timedOut : undefined ,
2369+ } ) ;
22522370 }
22532371 }
22542372 return processes . size > 0 ? processes : null ;
22552373 }
22562374
22572375 private serializeProcesses (
2258- processes : Map < string , { startTime : string ; command : string } > | null
2259- ) : Record < string , { startTime : string ; command : string } > | null {
2376+ processes : Map < string , SessionProcessEntry > | null
2377+ ) : Record < string , SessionProcessEntry > | null {
22602378 if ( ! processes || processes . size === 0 ) {
22612379 return null ;
22622380 }
2263- const serialized : Record < string , { startTime : string ; command : string } > = { } ;
2381+ const serialized : Record < string , SessionProcessEntry > = { } ;
22642382 for ( const [ pid , entry ] of processes . entries ( ) ) {
22652383 serialized [ pid ] = entry ;
22662384 }
0 commit comments