fix(tools): mid-turn steer releases a blocking process_manage wait (port kimi-code#3697) - #107858
Conversation
૮ >ﻌ< ა ci reviewran on 414e7b9 — fix(tools): steer/redirect releases a blocking process_manag
|
|
CI note: the |
f28d328 to
205645e
Compare
…ort of MoonshotAI/kimi-code#3697) A user message sent mid-turn (CLI busy_input_mode=interrupt, gateway priority redirect, ACP redirect) goes through AIAgent.redirect(), which during tool execution degrades to steer() + request_yield() on the tool worker threads. The local terminal backend's foreground wait honours the yield (adopting the process into the background registry), but ProcessRegistry.wait() — the process_manage(action='wait') path — never checked it: a model sitting in a wait on an already-background process parked the user's message for up to the full wait window (default 180s, clamp allows more). wait() now consumes a pending yield on its own thread each poll pass and returns status "interrupted" with process_running=true and a note telling the model to respond to the user; the process is untouched and still notify-tracked. The plain-interrupt and timeout paths are unchanged. Live repro: on origin/main, request_yield() against a thread blocked in wait(timeout=12) had no effect (wait ran to timeout); after this change the wait releases in <1s with status=interrupted, process still running. Port of MoonshotAI/kimi-code#3697 ("let steer interrupt background task waits") adapted to Hermes' per-thread yield mechanism from 4632923.
A user message sent while the model sits in
process_manage(action='wait')no longer waits out the full wait window — the wait releases in under a second, the process keeps running, and the model answers the user.Symptom
Mid-turn user messages (CLI
busy_input_mode=interrupt, gateway priority redirect, ACP redirect) route throughAIAgent.redirect(), which during tool execution degrades tosteer()+request_yield()on the tool worker threads. The local terminal backend's foreground wait honours that yield (4632923 adopts the live process into the background registry), butProcessRegistry.wait()— theprocess_manage(action='wait')path used to watch an already-background process (CI watchers, builds, servers) — never checked it. The steer text rides the tool result, so the user's message was parked until the wait timed out: up to 180s by default, more with a raisedTERMINAL_TIMEOUT.Change
ProcessRegistry.wait()consumes a pending yield on its own thread each poll pass (the same 1s-granularity loop that already checks interrupt) and returnsstatus="interrupted"withprocess_running: trueplus a note telling the model the process is untouched, still notify-tracked, and that it should respond to the user now.consume_yieldclears the bit, so subsequent waits behave normally.Port of MoonshotAI/kimi-code#3697 ("let steer interrupt background task waits" — their
WaitFortool had the same shape: steer added input for the next step but the active wait ran to its 600s cap), adapted to Hermes' per-thread yield mechanism.Live repro
request_yield(waiter_tid)against a thread blocked inwait(timeout=12)timeoutstatus=interrupted,process_running=true, process aliveReproduced and confirmed via real
ProcessRegistry.adopt_local+sleep 30subprocess with fresh imports against a tempHERMES_HOME(positive and negative: a wait with no yield still times out normally).Validation
TestWaitYieldRelease(2 invariant tests): yield releases the wait, keeps the process running, and clears the yield bit; a wait without a yield still times out.tests/tools/test_process_wait_clarity.py+tests/tools/test_process_registry.py: 105 passed, 0 failed.ruff checkclean; Windows-footgun scan clean; compat-pointer check clean.Infographic