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 1 commit into
Conversation
|
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 |
5af87a9 to
12494ca
Compare
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 and sets a topic-aware abort marker (`_aborted_topics: set[tuple[int, int | None]]`), runs under `_kill_lock` for parity with `kill_by_label`, and skips the marker when no targets match (no stale marker on empty stops). `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. - Abort recognition extended to topic scope: 5 sites in `orchestrator/flows.py` (`_maybe_recover_session`, `normal`, `normal_streaming`, `named_session_flow`, `named_session_streaming`) and 2 sites in `cli/service.py` (`execute_streaming`, `_handle_stream_fallback`) check `was_aborted_topic` alongside the existing `was_aborted` and `was_interrupted` checks. - `Orchestrator._handle_message_impl` clears the topic marker on the next message entry (parallel to existing chat-wide `clear_abort`). Background tasks and `drain_pending(chat_id)` remain chat-wide in this PR (background tasks are not topic-tagged in the current model; `drain_pending` is unchanged). Topic-aware versions can be added in follow-up issues if useful. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12494ca to
28e78f5
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)filters tracked processes by topic and sets a topic-aware abort marker (_aborted_topics: set[tuple[int, int | None]]). It runs under_kill_lockfor parity withkill_by_label, and skips the marker when no targets match so empty stops don't leak markers.kill_allis left as-is so/stop_alland shutdown paths keep their full sweep.Orchestrator.abort(chat_id, topic_id=None)runs the topic-scoped kill whentopic_idis provided; 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.orchestrator/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) checkwas_aborted_topicalongside the existingwas_abortedandwas_interruptedchecks. Without this, a topic-scoped kill leaves the chat-wide abort check at False, the SIGKILL response falls through to_reset_on_error, and chat-widekill_allruns — defeating the topic scope.Orchestrator._handle_message_implclears the topic marker on the next message entry (parallel to the existing chat-wideclear_abortatcore.py:328). Topic abort marker has no persistent state.If you'd prefer a different shape (e.g. a separate
/stop_topiccommand, or an opt-in config flag), happy to adjust.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 passedruff check,ruff format --check,mypyclean