feat(gateway): Phase 2 foundations — prompt sender_device param + advertised presence endpoints#131
Merged
Merged
Conversation
…ertised presence endpoints Channels Phase 2 groundwork (docs/channels-design.md): a client attached from ANOTHER device needs (a) a way to declare who typed a prompt and (b) a dialable address to find the hosting gateway in the first place. - tui_gateway/server.py: prompt.submit accepts optional sender_device (sanitized: whitespace-collapsed, 80-char cap); stashed on the session under history_lock and handed to the conversation loop per turn. - agent/conversation_loop.py: the turn's user message dict carries the declared sender; the API-message builder strips it for strict providers (same pattern as reasoning/finish_reason/_thinking_prefill). - run_agent.py: the session-DB flush passes sender_device through for user rows — explicit remote sender wins, absent still auto-stamps the local device (schema v17 behavior). - hermes_cli/web_server.py: when the dashboard binds beyond loopback, the session-presence records now advertise a dialable ws endpoint (HERMES_SESSION_PRESENCE_ENDPOINT via setdefault — operator config still wins; wildcard binds resolve the primary LAN IP via the UDP-connect trick; loopback binds advertise nothing). Zero-dep: works with no mesh/tailnet tooling; an explicit Tailscale/LAN bind advertises itself. - tests: 5 endpoint-advertisement cases + sanitizer cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔎 Lint report:
|
OmarB97
pushed a commit
that referenced
this pull request
Jun 10, 2026
… fork consolidation; finish fork-feature ports Per-cluster restoration with the test suite as the oracle, after comparing the merged tree's failures against a pristine-upstream run in the same environment (14 file-level deltas, now zero): - gateway/run.py: upstream wholesale (fork's monolith had undone the mixin decomposition; both real fork deltas re-applied — voice_ack_callback **kwargs; the custom-providers context-length fix exists upstream). - agent/conversation_loop.py + turn_context.py: upstream structure with the fork features regrafted at their new homes — sender_device attribution (#131), preflight token-usage emission + compression-complete status and live-estimate snapshots (#126). - agent/chat_completion_helpers.py: upstream wholesale (brings the second partial-stream-stub routing site and the NousResearch#6600 cancellation fix). - agent/tool_executor.py: usage= kwarg on tool start/complete callbacks now falls back to the bare 3-arg form for legacy receivers. - tools/approval.py: upstream's resolved-HERMES_HOME rewrite + normalize steps restored alongside the fork's self-host kill guard (#128). - hermes_cli/main.py: desktop install-identity stale-build cluster and the post-subcommand global-flag hoister ported from fork main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Why
Channels Phase 2 (cross-device attach, per
docs/channels-design.md) needs two foundations before any remote client can exist: a protocol way for that client to declare which device a prompt was typed on (otherwise every message in a shared session gets attributed to the hosting gateway's device), and a dialable address in the presence records so a second device that discovers a live session knows where to connect. Both are inert for today's local-only clients and activate the moment a remote client ships.What changed
tui_gateway/server.py:prompt.submitaccepts an optionalsender_deviceparam, sanitized by a new_sanitize_sender_device(non-strings → empty, whitespace collapsed, 80-char cap — it renders as a chat label, so it is not trusted verbatim). Stashed on the session underhistory_lock, popped by_run_prompt_submit, handed to the agent per turn.agent/conversation_loop.py: the turn's user-message dict carries the declared sender; the API-message builder stripssender_deviceexactly likereasoning/finish_reason/_thinking_prefillso strict providers (Mistral et al) never see it.run_agent.py: the session-DB flush passessender_devicefor user rows — explicit remote sender wins; absent staysNonesoappend_message's local-device auto-stamp (schema v17) applies unchanged.hermes_cli/web_server.py: new_presence_advertise_endpoint(host, port)+_primary_lan_ip(). Non-loopback dashboard binds advertisews://<addr>:<port>/api/wsintoHERMES_SESSION_PRESENCE_ENDPOINTviasetdefault(operator-set env always wins). Wildcard binds resolve the primary outbound IPv4 via the UDP-connect trick (no packet sent, no DNS, no mesh tooling); loopback binds advertise nothing because the address is meaningless off-machine.tests/cli/test_presence_advertise_endpoint.py(5 cases) + sanitizer cases intest_concurrent_attach.py.How to review
_sanitize_sender_device+ theprompt.submitstash and_run_prompt_submitpop (one lock-scoped handoff).conversation_loop.py— the user-dict tag + the API strip; confirm the strip sits with its four siblings.run_agent.pyflush — confirmNonefor non-user rows preserves assistant/tool NULL semantics.web_server.py— the two helpers and thesetdefaultat startup (placed right afterapp.state.bound_host/port).Evidence
test_wildcard_bind_without_detectable_ip_advertises_nothingpins the fail-closed behavior;test_explicit_sender_wins_over_auto_stamp(from feat(state): messages.sender_device — device-level sender attribution (F-003 slice 1) #129) already pins the DB-level override this feeds.Verification
tests/cli/test_presence_advertise_endpoint.py+tests/tui_gateway/test_concurrent_attach.py+tests/tui_gateway/test_protocol.py+tests/test_sender_attribution.py— 79 passed.py_compileclean on all four touched modules.Risks / gaps
F-003-multi-participant-channels.prompt.backgrounddoes not yet acceptsender_device— accepted scope, background prompts are gateway-originated (no remote typer) today./api/ws— low risk, it is the only mount point and the constant lives beside the helper.docs/channels-design.md(the--insecureopt-in contract is unchanged here) — covered by the Phase 2 listen-auth item onF-003-multi-participant-channels.Collaborators