The plugin-command dispatch binds the live SessionSource (receiving-bot
provenance included) task-locally, so a post from a command that is the
chat first message goes through the bot that received it instead of a
source rebuilt from session env vars.
- Sync edit() refuses on the gateway loop before any wait on a pending
operation (the wait would block the loop that operation needs).
- A first-message route is rebuilt through _plugin_origin_source, which
stamps the receiving bot provenance and checks the profile, like an
inject_message origin.
- Branch on SendResult.success, not on an empty error string.
- Mark every post/fallback send _interim_send so a relay stream open for the
turn's answer is never sealed with plugin progress.
- Rebuild a first-message source from every field the gateway binds
(scope_id, user_id_alt, parent_chat_id, ...) so it keys like the live one.
- A send past the 30 s wait keeps running as the handle's pending
operation; its result still lands on the handle and the next edit waits
for it, so a slow post never leaves an orphan message.
- An edit_message that raises falls back to a fresh send like a refused one.
A plugin can now post a status message into the conversation it is working
for and update that same message later, on any channel, with no
per-platform code (#133306).
- ctx.post_message(text) -> PluginMessage; handle.edit(text). Async twins
ctx.apost_message / handle.aedit for code on the gateway loop.
- The conversation is the session bound to the calling tool call, slash
command or hook; the plugin never names a chat. No bound session (timer,
plugin thread, cron) or no live gateway -> ok=False with an error code,
never an exception.
- Messaging chats go through the answering adapter's send/edit_message.
Adapters without edit support, or a refused edit, get a fresh send and
the handle follows the new message (edited=False, fallback=True).
- Local CLI/TUI/Desktop sessions get a keyed notification.show that
Desktop and the Ink TUI replace in place.
- Refused in the out-of-process plugin host (handle is loop-bound).
- Every post/edit is logged as plugin_message plugin=... op=post|edit.