fix(cron): deliver proactive agent final text to the bound session - #10008
fix(cron): deliver proactive agent final text to the bound session#10008xiaoyuyu6420 wants to merge 4 commits into
Conversation
Cron and background-task wakeups consumed runner.step_until_done() with a discarded iterator, so the final assistant text was only folded into the persisted history summary and never sent to the delivery session. At forced wrap-up (max steps) all tools are removed, so the model cannot use send_message_to_user either — the user only ever saw intermediate messages (AstrBotDevs#9980). Deliver the final assistant text through the cron event after history persistence on both wake paths, skipping when the model already sent this exact text to the current session via send_message_to_user during the same run (recorded by the tool as SENT_TO_CURRENT_SESSION_PLAIN_TEXTS_EXTRA_KEY). A failed send is logged but does not fail the job. Signed-off-by: xiaoyuyu6420
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/cron/manager.py" line_range="546-558" />
<code_context>
logger.warning("background task agent got no response")
return
+ final_text = (llm_resp.completion_text or "").strip()
+ if llm_resp.role == "assistant" and final_text:
+ # Same delivery gap as the cron path (#9980): the final text only
+ # lands in persisted history unless it is explicitly sent. Skip
+ # when the model already delivered this exact text via
+ # send_message_to_user earlier in the same run.
+ already_sent = cron_event.get_extra(
+ SENT_TO_CURRENT_SESSION_PLAIN_TEXTS_EXTRA_KEY, []
+ )
+ if final_text not in already_sent:
+ try:
+ await cron_event.send(MessageChain().message(final_text))
+ except Exception as e: # noqa: BLE001
+ logger.warning(
</code_context>
<issue_to_address>
**issue (bug_risk):** The final response is sent through `cron_event`, whose session was constructed from `session_str`, while the new condition only checks that `delivery_session_str` is non-empty. When those values differ, the response is delivered to the cron/history session instead of the configured delivery session, so the bound recipient still receives no final result.
**Triggers:** When a cron job's `session_str` and `delivery_session_str` are different.
**Suggested fix:** Construct the delivery event/session from `delivery_session_str`, or send the final message through an API that explicitly targets that session; use the same target when recording duplicate-delivery metadata.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and the change explicitly sends the agent's final response to the bound user session, so an incorrect response or session binding can produce an unintended external message that cannot be retracted. Reverting prevents future messages but does not undo messages already delivered.
Blocking findings: astrbot/core/cron/manager.py:558
Address Sourcery bug_risk: delivery went through cron_event (built from session_str) while the gate checked delivery_session_str. When a caller passes differing values the text would land on the wrong session. Send via Context.send_message(delivery_session_str, ...) so the gate and the target are the same variable; no-platform and failure outcomes are logged without failing the job. Signed-off-by: xiaoyuyu6420
|
Addressed the Sourcery bug_risk in a11ce12: the cron path now delivers via Added |
Review feedback on AstrBotDevs#9980: blanket delivery of the final assistant text also fired on normally completed runs, where the model still has send_message_to_user available and may intentionally stay silent (e.g. conditional notify-only cron jobs). DONE alone does not imply the user should be notified. ToolLoopAgentRunner now exposes reached_max_steps, set only in the forced final-response branch of step_until_done where all tools were removed and the model had no channel to deliver its summary. Cron and background-task wakeups deliver the final text only in that case; normal completion keeps the current silent behavior. Signed-off-by: xiaoyuyu6420
reached_max_steps was only initialized in reset(), so reading the property on a fresh runner raised AttributeError instead of returning the documented default. Add a class-level False default and tests covering both the forced wrap-up and normal-completion paths against a real runner. Signed-off-by: xiaoyuyu6420
kilisamemarisaaa
left a comment
There was a problem hiding this comment.
Reviewed the current head and the forced-wrap-up delivery path. The
eached_max_steps guard is set only where tools are removed, while normal completion remains silent; the cron path now targets delivery_session_str directly and the duplicate-text check is scoped to the current event. The added tests cover target mismatch, duplicate suppression, invalid responses, delivery failure, and the runner flag, and the full CI matrix is green. I found no blocking issue in the current diff.
|
Closing this to avoid a dangling PR: Soulter closed #9980 as completed on Sep 12. I checked current master to confirm whether this is still needed before closing:
Since the issue is closed and there has been no maintainer review, the honest state is that this half is currently not in scope. The branch |
Problem
Proactive agent runs (cron
active_agentjobs and background-task result wakeups) discard everyAgentResponse: both call sites consumerunner.step_until_done()withasync for _ ... pass. The final assistant text is only folded into the persisted history summary — it is never sent to the delivery session.Two details make this worse and turn it into the exact symptom reported in #9980 ("only intermediate messages, no final result"):
ToolLoopAgentRunner.step_until_done, max steps reached) all tools are removed before the final step, so the model cannot usesend_message_to_userto deliver its forced summary.SendMessageToUserTool's description says "For other normal text replies, you can output directly and no need to use this tool" — models that follow that hint output plain text, which is exactly the text being discarded.This is the remaining gap from the #9980 breakdown; #9987 fixed the ERROR-state propagation and #9992 fixed compression-config sharing. This PR fixes final-response delivery for both wake paths.
Fix
CronJobManager._woke_main_agent()andAstrAgentToolExec._wake_main_agent_for_background_result()now send the final assistant text through the cron event afterpersist_agent_history(), when:llm_resp.role == "assistant"with non-empty text (error responses are never pushed to chats);send_message_to_userearlier in the same run.SendMessageToUserToolalready writes to the event; the key is now a shared constant (SENT_TO_CURRENT_SESSION_PLAIN_TEXTS_EXTRA_KEY).Testing
tests/unit/test_cron_manager.py(TestWokeMainAgentFinalDelivery):Fixes part of #9980 (final-response delivery half).
Summary by Sourcery
Deliver final assistant text from forced proactive-agent wrap-ups to their bound sessions without duplicating messages or affecting job success.
New Features:
Bug Fixes:
Enhancements:
Tests: