mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 22:23:07 +00:00
`turn/interrupt` ends a Codex turn and leaves the shell commands it started running: a command the user just stopped runs on to its own end, alongside the next turn. Once Codex accepts the interrupt, the provider now ends each command the interrupted turn started that has not exited, by process id, with `thread/backgroundTerminals/terminate`, and a turn Paseo starts waits for those calls before its `turn/start`. - Attribution: `thread/backgroundTerminals/list` carries no turn, so the provider records each root-thread `commandExecution` process id under the turn of its `item/started`. A `completed` item drops its id; a `failed` one keeps it, because Codex reports an interrupted turn's still-running command as failed. A turn that ends any way but `interrupted` forgets its ids, so what an earlier turn left running on purpose is never ended. At most 16 turns are remembered. - A late command: the turn keeps its accepted interrupt, so a command whose `item/started` arrives after Codex accepted the interrupt (one queued behind an earlier notification's conversion, for instance) is ended as soon as it is seen. Each process is terminated once, and a turn start waits for every pending sweep. - It never sends `thread/backgroundTerminals/clean`, which ends every terminal on the thread. It ends nothing after an already-idle interrupt, nothing on a subagent's thread, and nothing after an interrupt of a turn Codex never identified. - Bounds: each terminate times out after 2 s; a failure is logged (`provider.codex.interrupt.terminal_terminate_failed`) and fails nothing. `startTurn` waits at most the interrupt's bound plus one terminate's plus 500 ms. Tests (codex-interrupted-turn-terminals.test.ts) drive a real CodexAppServerAgentSession against the scripted app-server in codex/test-utils/fake-app-server.ts: exactly the interrupted turn's processes are terminated, by id, and `clean` is never sent; a turn Codex starts before acknowledging the interrupt keeps its commands; the next `turn/start` waits for the terminates; a command announced after the interrupt was accepted is terminated once and the next turn waits for both sweeps, whichever answers first; a completed command is forgotten and a failed one kept; only the latest sixteen accepted interrupts are kept; a failing or unanswered terminate does not block the next turn. The harness waits for the interrupted turn's `turn_canceled` before it starts the next turn, as a caller does: notifications are handled asynchronously, so the interrupt's answer can arrive before the turn has ended. Fixes #7522 Claude-Session: https://claude.ai/code/session_01Ay568T2kmtotoh23Dtzi5K Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>