[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:
ekko
2026-05-05 13:03:14 +08:00
committed by GitHub
parent f13ce3a080
commit e3d28f4659
8 changed files with 524 additions and 231 deletions
@@ -133,7 +133,7 @@ watch(currentToolCalls, () => {
:highlight="chatStore.focusMessageId === msg.id"
/>
<Transition name="fade">
<div v-if="chatStore.isRunActive" class="streaming-indicator">
<div v-if="chatStore.isRunActive || chatStore.abortState" class="streaming-indicator">
<video
:src="isDark ? thinkingVideoDark : thinkingVideoLight"
autoplay
@@ -142,7 +142,47 @@ watch(currentToolCalls, () => {
playsinline
class="thinking-video"
/>
<div v-if="currentToolCalls.length > 0 || chatStore.compressionState" class="tool-calls-panel">
<div v-if="currentToolCalls.length > 0 || chatStore.compressionState || chatStore.abortState" class="tool-calls-panel">
<!-- Abort indicator -->
<div v-if="chatStore.abortState" class="tool-call-item compression-item">
<svg
v-if="chatStore.abortState.aborting"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="tool-call-icon"
>
<path d="M10 9v6m4-6v6M5 5h14v14H5z" />
</svg>
<svg
v-else
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="tool-call-icon"
>
<path d="M5 13l4 4L19 7" />
</svg>
<span class="tool-call-name">
{{
chatStore.abortState.aborting
? 'Pausing... waiting for the run to stop and sync'
: chatStore.abortState.synced
? 'Paused and synced'
: 'Paused'
}}
</span>
<span
v-if="chatStore.abortState.aborting"
class="tool-call-spinner"
></span>
</div>
<!-- Compression indicator -->
<div v-if="chatStore.compressionState" class="tool-call-item compression-item">
<svg