Repository navigation
fix(bot-mode): deliver local DMs into a live Bot Chat owner instead of refusing - #103437
MestreY0d4-Uninter wants to merge 1 commit into
Conversation
…f refusing The local message_agent path spawns 'hermes -p <profile> chat' for the target's canonical Bot Chat; when this gateway hosts that chat live, the single-owner lease fences the subprocess out and the DM is refused (target_busy) with no turn ever run — one background tab silently black-holes all DM/cron delivery (NousResearch#103030). Surfaces that host live sessions now register a deliverer: the tool submits through prompt.submit(queued=True) — runs as the next turn, never interrupts one in flight — mirroring bot_relay.deliver's relay-path handoff (NousResearch#101293). No live session → the subprocess transport runs exactly as before.
Related: this competes with open #101564 for the same local Bot Mode DM delivery gap (#101060 / #100523, background-tab lease in #103030). Different mechanisms: #101564 uses a durable pending/claim/receipt handoff drained by the TUI idle poller; this PR registers a live-owner deliverer hook and submits via |
|
Thanks @MestreY0d4-Uninter for identifying the local-versus-relay gap and proposing queued handoff to the existing Bot Chat owner. That user-facing behavior is now covered by #105442 (main Closing this alternative as superseded, with thanks for making the queued live-owner path explicit. This is coverage for an advertised live consumer, not a claim that arbitrary CLI-only owners or unrelated exact-session IPC are supported. Older running backends must restart after upgrading. |
What does this PR do?
Bot Mode local DM delivery (
message_agent→hermes -p <profile> chat -c "Bot Chat") is refused withtarget_busywhenever the target's canonical Bot Chat session is live in this gateway — even when nobody is viewing that chat and no turn is running. One background Desktop tab silently black-holes all DM/cron delivery to that teammate.Surfaces that host live Bot Chat sessions now register a live-owner deliverer:
message_agenthands the DM to the existing owner viaprompt.submit(queued=True)— the composer choke point, so role alternation, persistence, and streaming behave as a typed message, and the DM runs as the NEXT turn (never interrupts a turn in flight) — instead of spawning a competing CLI that the single-owner lease fences out. With no live session for the target profile, the subprocess transport runs exactly as before.This completes for the local delivery path the live-owner handoff that #101293 landed for the relay path (
bot_relay.deliver); the single-owner fence itself is untouched.Mechanism
tools/bot_mode_dm.pygains aregister_live_dm_deliverer(fn)hook registry (same inversion pattern astools/approval's_gateway_notify_cbs— tools never import the server hosting the sessions).message_agent_toolconsults the hooks before spawning the subprocess; the first accepted ack returns synchronously to the sender;Nonefalls through to the subprocess transport unchanged.tui_gateway/server.pyimplements the deliverer:_live_bot_chat_session_for_profile(profile)(factored out ofbot_relay.deliver's inline lookup so both paths agree on what "the target's Bot Chat is live here" means) +_deliver_dm_to_live_session, which submits viaprompt.submit(queued=True)and returns a truthful ack. Aprompt.submiterror defers to the subprocess transport's error shaping (target_busy refusal + retry policy) rather than half-acknowledging.Related Issue
Closes #103030 (follow-up to #100523; completes #101293 for the local path — the maintainer-endorsed "deliver through the live owner" direction)
How was this tested?
scripts/run_tests.sh tests/tools/ tests/test_tui_gateway_server.py tests/tui_gateway/— 10,168 passed; the 7 failures are pre-existing/flaky on this machine and reproduce identically on a pristinemainworktree (voice_mode/pulse-audio sockets, STT/compute-host timing, delegate/zombie cleanup retries,test_tui_gateway_server::test_model_options_preserves_canonical_custom_row_after_agent_init).main@ 79445a4 (all 6 fail there withAttributeErroron the missing mechanism, then pass with the fix):tests/test_tui_gateway_bot_dm_live_owner.py— registration wiring; live Bot Chat →prompt.submit(queued=True)+ truthful ack; no live session →None(subprocess fallback);prompt.submiterror →None(subprocess transport owns the error shaping).tests/tools/test_bot_mode_dm.py::test_live_owner_delivery_skips_subprocess/::test_live_owner_delivery_falls_back_when_no_live_session— hook preferred over subprocess (zero spawns), fallback intact when the deliverer reports no live session.ruff checkon touched files;scripts/check-windows-footguns.pyclean.Platforms
Linux (tested). The change is transport-layer Python with no platform-specific I/O; the refusal path is unchanged for remote/peer deliveries and for profiles with no live session.