[codex] Handle chat run abort lifecycle (#454)
* feat: call upstream stop API when aborting a run
- Modified handleAbort to call POST /v1/runs/{run_id}/stop endpoint
- Use profile-specific upstream URL and API key from gatewayManager
- Add 5-second timeout with error handling and logging
- Keep local abortController.abort() for EventSource cleanup
- Change handleAbort to async method and update call site
This ensures the upstream Hermes gateway is properly notified
when a user aborts a run, allowing graceful termination.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: close ChatRunSocket connections on shutdown to prevent hanging
- Add close() method to ChatRunSocket to abort all active runs
and clear session state
- Pass chatRunServer to bindShutdown and close it before
groupChatServer during shutdown
- This prevents EventSource connections and abort controllers
from keeping the process alive during nodemon restart
Fixes the "still waiting for sub-process to finish" issue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Handle chat run abort lifecycle
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,8 @@ const sessionEventHandlers = new Map<string, {
|
||||
onRunFailed: (event: RunEvent) => void
|
||||
onCompressionStarted: (event: RunEvent) => void
|
||||
onCompressionCompleted: (event: RunEvent) => void
|
||||
onAbortStarted: (event: RunEvent) => void
|
||||
onAbortCompleted: (event: RunEvent) => void
|
||||
onUsageUpdated: (event: RunEvent) => void
|
||||
}>()
|
||||
|
||||
@@ -223,6 +225,34 @@ function globalCompressionCompletedHandler(event: RunEvent): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global abort.started event handler
|
||||
*/
|
||||
function globalAbortStartedHandler(event: RunEvent): void {
|
||||
const sid = event.session_id
|
||||
if (!sid) return
|
||||
|
||||
const handlers = sessionEventHandlers.get(sid)
|
||||
if (handlers?.onAbortStarted) {
|
||||
handlers.onAbortStarted(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global abort.completed event handler
|
||||
*/
|
||||
function globalAbortCompletedHandler(event: RunEvent): void {
|
||||
const sid = event.session_id
|
||||
if (!sid) return
|
||||
|
||||
const handlers = sessionEventHandlers.get(sid)
|
||||
if (handlers?.onAbortCompleted) {
|
||||
handlers.onAbortCompleted(event)
|
||||
}
|
||||
|
||||
sessionEventHandlers.delete(sid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Global usage.updated event handler
|
||||
*/
|
||||
@@ -256,6 +286,8 @@ export function registerSessionHandlers(
|
||||
onRunFailed: (event: RunEvent) => void
|
||||
onCompressionStarted: (event: RunEvent) => void
|
||||
onCompressionCompleted: (event: RunEvent) => void
|
||||
onAbortStarted: (event: RunEvent) => void
|
||||
onAbortCompleted: (event: RunEvent) => void
|
||||
onUsageUpdated: (event: RunEvent) => void
|
||||
}
|
||||
): () => void {
|
||||
@@ -333,6 +365,8 @@ export function connectChatRun(): Socket {
|
||||
// Compression events
|
||||
chatRunSocket.on('compression.started', globalCompressionStartedHandler)
|
||||
chatRunSocket.on('compression.completed', globalCompressionCompletedHandler)
|
||||
chatRunSocket.on('abort.started', globalAbortStartedHandler)
|
||||
chatRunSocket.on('abort.completed', globalAbortCompletedHandler)
|
||||
|
||||
// Usage events
|
||||
chatRunSocket.on('usage.updated', globalUsageUpdatedHandler)
|
||||
@@ -361,7 +395,7 @@ export function disconnectChatRun(): void {
|
||||
*/
|
||||
export function resumeSession(
|
||||
sessionId: string,
|
||||
onResumed: (data: { session_id: string; messages: any[]; isWorking: boolean; events: any[]; inputTokens?: number; outputTokens?: number }) => void,
|
||||
onResumed: (data: { session_id: string; messages: any[]; isWorking: boolean; isAborting?: boolean; events: any[]; inputTokens?: number; outputTokens?: number }) => void,
|
||||
): Socket {
|
||||
const socket = connectChatRun()
|
||||
|
||||
@@ -436,6 +470,16 @@ export function startRunViaSocket(
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
},
|
||||
onAbortStarted: (evt: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
},
|
||||
onAbortCompleted: (evt: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
closed = true
|
||||
onDone()
|
||||
},
|
||||
onUsageUpdated: (evt: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
@@ -452,8 +496,6 @@ export function startRunViaSocket(
|
||||
return {
|
||||
abort: () => {
|
||||
if (!closed) {
|
||||
closed = true
|
||||
sessionEventHandlers.delete(sid)
|
||||
socket.emit('abort', { session_id: sid })
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user