fix(stop): scope /stop to the current forum topic when present#136
fix(stop): scope /stop to the current forum topic when present#136ryuhaneul wants to merge 2 commits into
Conversation
In a Telegram supergroup with multiple forum topics, `/stop` ends every active CLI process in the chat plus every background task and named session. Users running parallel work across topics see unrelated work interrupted. This change scopes `/stop` to the message thread when one is present; when no thread is set the existing chat-wide branch runs unchanged so non-forum chats and legacy callers see no behavior change. `/stop_all` is untouched. - `ProcessRegistry.kill_by_chat_topic(chat_id, topic_id)` filters tracked processes by topic. `kill_all` is unchanged so `/stop_all` and shutdown paths keep their chat-wide sweep. - `Orchestrator.abort(chat_id, topic_id=None)` runs the topic-scoped kill when `topic_id` is provided; otherwise the existing branch (foreground CLIs + background tasks + named sessions) runs. - `handle_abort` reads `thread_id` from the message and passes it as `topic_id`. `handle_abort_all` is untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Self-flag: potential abort-state issue uncovered during downstream review. After scoping Concretely: a user issuing Converting to draft until the abort flow is reconciled with the new topic scope. Likely fix direction: have |
Add a topic-aware abort marker for ProcessRegistry.kill_by_chat_topic so /stop in a forum topic is recognized by flow and streaming abort checks without setting the chat-wide abort marker. Topic abort marker is cleared on the next message entry to the same topic (parallel to existing chat-wide _aborted clear at core.py:328). No persistent state. Background tasks and pending queue drain remain chat-wide in this PR (background tasks are not topic-tagged per the current model; drain_pending is unchanged). Topic-aware versions can be added in follow-up issues if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5af87a9 to
12494ca
Compare
Background
When using ductor in a Telegram supergroup with multiple forum topics, I sometimes have parallel sessions running across different topics (e.g. one topic for research, another for design review). Typing
/stopin one topic currently ends every active CLI process in the entire chat, plus every background task and named session — including the work happening in the other topics that I did not intend to stop.It would be more convenient if
/stoponly stopped the in-flight response in the topic where it was sent, leaving the other topics' work and background machinery (which already have/tasksand/sessionsfor management) untouched./stop_allcontinues to cover the chat-wide case.Proposed change
When the inbound message carries a
message_thread_id, scope/stopto that thread; otherwise keep the current chat-wide behavior so non-forum chats and legacy callers see no change.Implementation:
ProcessRegistry.kill_by_chat_topic(chat_id, topic_id)— filter tracked processes by topic.kill_allis left as-is so/stop_alland shutdown paths keep their full sweep.Orchestrator.abort(chat_id, topic_id=None)— whentopic_idis provided, run only the topic-scoped kill; otherwise the existing chat-wide branch runs (background tasks + named sessions included, exactly as today).handle_abortreadsthread_idfrom the message and passes it astopic_id.handle_abort_allis untouched.If you'd prefer a different shape (e.g. a separate
/stop_topiccommand, or an opt-in config flag), happy to adjust.Reinforcement (commit 12494ca)
Downstream review found a gap in the original commit:
kill_by_chat_topicdid not set an abort marker the way other kill paths do (kill_all→_aborted,kill_by_label→_aborted_labels). Without a marker,flows.py'swas_aborted(chat_id)check returned False, the SIGKILL response was treated as an error, and_reset_on_error()ran chat-widekill_all— defeating the topic scope.Added a topic-aware abort marker:
ProcessRegistry._aborted_topics: set[tuple[int, int | None]]+was_aborted_topic+clear_topic_abortkill_by_chat_topicnow sets the marker (only when targets actually exist, so empty stops don't leak markers) and runs under_kill_lockfor parity withkill_by_labelorchestrator/flows.py(_maybe_recover_session,normal,normal_streaming,named_session_flow,named_session_streaming) and 2 sites incli/service.py(execute_streaming,_handle_stream_fallback) now also checkwas_aborted_topicOrchestrator._handle_message_implclears the topic marker on the next message entry (parallel to existing chat-wideclear_abortatcore.py:328)Topic abort marker has no persistent state; it lives until the next message in the same topic clears it.
Out of scope
Background tasks and
drain_pending(chat_id)remain chat-wide in this PR — background tasks are not topic-tagged in the current model, and the pending queue drain has its own follow-up scope. Topic-aware versions can be added in follow-up issues if useful.Test plan
kill_by_chat_topicmarker behavior, Topic A kill not killing Topic B,_maybe_recover_sessionskipping recovery on topic abort,handle_abortpropagatingtopic_id, andnamed_session_flowtopic aborttests/cli/test_process_registry.py + tests/orchestrator/test_flows.py + tests/messenger/telegram/test_handlers.py + tests/cli/test_service.py) — 108 passed (no regression on existing PR tests)ruff check,ruff format --check,mypyclean