fix(acp): wire stream_delta_callback for real-time ACP text delta streaming - #13810
Closed
Git-on-my-level wants to merge 21 commits into
Closed
fix(acp): wire stream_delta_callback for real-time ACP text delta streaming#13810Git-on-my-level wants to merge 21 commits into
Git-on-my-level wants to merge 21 commits into
Conversation
…t, suppress build stderr Group 1 patches re-applied on upstream main (e94008c): - hermes_cli/update_channel.py: new channel detection (prod vs main) - hermes_cli/main.py: channel-aware fetch/ref in cmd_update, prod pre-sync - hermes_cli/gateway.py: launchd_restart_for_update with healthcheck - hermes_cli/banner.py: channel display in banner - hermes_cli/main.py: capture_output=True on .[all] install fallback - tests updated for new contract
…ompression - telegram.py: _is_nonretryable_auth_error() detects 401/Unauthorized, sets retryable=False to prevent infinite retry loops on bad tokens - run.py: /usage command counts all message roles (not just user/assistant), preventing 2-5x undercount in tool-heavy conversations - run.py: pass full transcript to compressor instead of filtering to user/assistant only, allowing compressor to prune stale tool results
…del runtime - Interrupt handler now checks if queued message is a command (starts with /) and dispatches it via _handle_message instead of treating as plain text - Session-scoped model overrides from /model now flow through to _run_agent's agent creation via _resolve_session_runtime_config() - Fallback model tracking uses session-aware model instead of global default
Add allowed_inbound_targets config support to TelegramAdapter for filtering inbound messages to specific chat/topic combinations. - Parse config from extra.allowed_inbound_targets (dict, string, int forms) - _is_inbound_target_allowed: allowlist check, DMs always pass - _is_inbound_message_allowed: convenience wrapper for PTB Message objects - Guard all four inbound handlers: text, command, location, media - Add tests for parsing, filtering, and handler rejection - Document in telegram.md with YAML examples and behavior notes
Merge-based updates caused compounding conflicts over time and required force-push to recover from anyway. Rebase keeps prod as a clean linear stack of patches on top of upstream main. - Uses --force-with-lease for safer pushes - Warns on large drift (>100 commits, >20 patches) - Pauses on rebase conflicts for manual resolution
_requested_turn is defined inside the run_sync() closure but was referenced from the outer _run_agent() async scope, causing a NameError on every turn where the agent has a model attribute.
Conflict resolutions: - gateway/run.py compression: keep prod model resolution + full history - gateway/run.py /status: keep prod full-history estimation with last_prompt_tokens - gateway/run.py fallback eviction: take upstream _run_failed + _is_intentional_model_switch - gateway/run.py pending handling: take upstream _dequeue_pending_event + command safety net - gateway/run.py queue fallback: take upstream merge_pending_message_event - gateway/run.py _run_agent recurse: pass both session_overrides (prod) + event_message_id (upstream) - hermes_cli/gateway.py: keep prod launchd helper functions + upstream _wait_for_gateway_exit signature
Switch prod pre-sync and update-prod-branch.sh from tracking origin/main to merging only the latest v* release tag. This keeps prod on stable upstream releases rather than pulling in every post-tag commit. - Add _latest_upstream_tag() helper that fetches tags and resolves the highest v* tag via --sort=-version:refname - _attempt_prod_prefetch_sync() now merges the latest tag (not origin/main) and skips sync if fork/prod already contains it - update-prod-branch.sh rebases onto the latest tag, exits early if prod is current, and reports tag-relative patch counts - Update tests and hermes-fork-prod skill docs to match new behavior
Upstream changes absorbed (no prod-only conflict): - run.py: upstream now has session-scoped /model overrides, full-history flush with live memory guard, busy-ack, deprecated cwd warnings, _parse_session_key, channel prompts, try/except in flush agent Prod-only re-applied on upstream base: - telegram.py: inbound-topic allowlist (_parse_allowed_inbound_targets, _is_inbound_target_allowed, _is_inbound_message_allowed) wired into _should_process_message for all message types - telegram.py: non-retryable auth error detection (_is_nonretryable_auth_error) with telegram_invalid_token fatal error (non-retryable) vs connect_error (retryable)
… ACP Wires AIAgent.interim_assistant_callback in the ACP adapter so editor clients (CAR, VS Code, Zed) receive interim assistant commentary messages during tool execution. Commentary is emitted as agent_message_chunk updates with _meta.phase set to commentary, following the ACP schema extension pattern. Fixes the gap where gateway-path commentary works but ACP-path drops it.
…eaming Root cause: ACP turns emitted partial session/update progress (thinking spinners, tool events) then went silent for the entire duration of run_conversation() because stream_delta_callback was never wired in the ACP adapter. The LLM's streamed text deltas were silently discarded since _fire_stream_delta() only dispatches to stream_delta_callback and _stream_callback — neither of which ACP set. Fix: Add make_stream_delta_cb() factory that forwards each text delta to conn.session_update() as an agent_message_chunk, and wire it as agent.stream_delta_callback in prompt(). This gives ACP clients (CAR/PMA/VS Code) real-time token-by-token progress during LLM streaming, eliminating the multi-minute silent gap before final prompt_return delivery. Fixes #9
Contributor
Author
|
Sorry forgot to cherry pick |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root Cause
ACP turns emit partial
session/updateprogress (thinking spinners, tool start/complete events, interim commentary), then go silent for the entire duration ofrun_conversation()before the final output arrives viaprompt_returninstead of a streamed terminal lifecycle event.Why:
stream_delta_callbackwas never wired in the ACP adapterThe ACP adapter (
acp_adapter/server.py) sets several callbacks onAIAgent:run_agent.py?thinking_callbacktool_progress_callbackstep_callbackinterim_assistant_callback_emit_interim_assistant_message)message_callbackstream_delta_callback_fire_stream_delta)The critical gap:
_fire_stream_delta()(line 5211 inrun_agent.py) is the method that fires for every LLM streaming token. It only dispatches tostream_delta_callbackand_stream_callback. Since ACP sets neither, every text delta from the LLM is silently discarded.CAR's
completion_source="prompt_return"(notstream_terminal_event) confirms no timely terminal lifecycle signal was received — it only got the final result through the RPC return path, ~30 minutes after the last progress update.Fix
make_stream_delta_cb()factory inacp_adapter/events.pythat forwards each text delta toconn.session_update()as anagent_message_chunkagent.stream_delta_callbackin theprompt()methodFiles Changed
acp_adapter/events.py— newmake_stream_delta_cb()factory (+17 lines)acp_adapter/server.py— wire callback inprompt()(+4 lines)tests/acp/test_events.py—TestStreamDeltaCallbackclass (+28 lines)tests/acp/test_server.py— integration testtest_prompt_wires_stream_delta_callback(+30 lines)Fixes #9