Files
unsupportedpastelsandBearHuddleston 7dbe1bff8f fix: keep Daybreak to the owner's local surfaces
The profile switch reached every turn the gateway ran, so turning it on in
Desktop also sent Daybreak on that profile's Telegram, Discord and Slack
chats. OpenAI allows Daybreak no third-party or downstream access, and a
bot's chats can be other people.

- gateway: _load_daybreak(source) is true only for Platform.LOCAL turns
  (Desktop, TUI, CLI).
- standalone hermes serve: agents built for a non-local source (a plugin
  session tagged telegram) never carry it, and a live flip skips them
  (agent.session_source.DAYBREAK_SOURCES).
- docs: say who gets it and why messaging chats don't.

Co-authored-by: BearHuddleston <196404565+BearHuddleston@users.noreply.github.com>
2026-11-02 15:06:27 -05:00

62 lines
3.3 KiB
Python

"""Session source vocabulary: the ``source`` label a session row carries, and which sources have a
person reading the turn as it runs. A leaf (its one import is the stdlib-only session context), so
the ``run_agent`` facade and its siblings import it directly."""
from __future__ import annotations
from typing import Any, Optional
from gateway.session_context import get_session_env
# Sources that label the human conversation an interactive UI transport hosts. A finite ``hermes chat -q`` /
# one-shot child spawned from such a session inherits HERMES_SESSION_SOURCE (the terminal tool bridges the
# session env into child processes) but is NOT that conversation: labelling it ``tui``/``desktop`` lists it
# in the TUI/WebUI pickers as a resumable chat and lets ``hermes -c`` in the TUI continue it (#112550).
# Automation sources (tool, a2a, ...) are inherited on purpose; a Kanban worker's descendants drop its
# gateway-only ``kanban`` label (delegation_context.scrub_kanban_env).
UI_TRANSPORT_SOURCES = frozenset({"tui", "desktop"})
# Finite non-interactive CLI runs (``hermes chat -q``/``--oneshot``, ``hermes -z``) get their own source so human
# pickers hide them without title/cwd heuristics; ``hermes -c`` still treats them as CLI history.
ONESHOT_SOURCE = "oneshot"
CLI_FAMILY_SOURCES = frozenset({"cli", ONESHOT_SOURCE})
# The owner's own local surfaces: the only sources that carry OpenAI Daybreak (agent.daybreak). It is the
# approved account's own access (no third-party or downstream use); a plugin session tagged ``telegram`` is
# someone else's chat even when a local backend builds its agent.
DAYBREAK_SOURCES = UI_TRANSPORT_SOURCES | {"cli"}
# A person reads the turn as it runs: not cron, batch, a messaging chat, or a one-shot run.
ATTENDED_SOURCES = UI_TRANSPORT_SOURCES | {"cli", "acp"}
def create_surface(source: str) -> str:
"""The ``session.create`` label for a session source: ``desktop`` is the Desktop's platform name for
the ``gui`` surface (gateway.session_policy.SURFACES), which the create contract does not list."""
return "gui" if source == "desktop" else source
def session_source_for(platform: Optional[str]) -> str:
source = str(get_session_env("HERMES_SESSION_SOURCE", "") or "").strip()
single_query = get_session_env("HERMES_SINGLE_QUERY_SESSION", "") == "1"
explicit = get_session_env("HERMES_SESSION_SOURCE_EXPLICIT", "") == "1"
if single_query and not explicit and source in UI_TRANSPORT_SOURCES:
source = ""
if single_query and not source and (platform or "cli") == "cli":
return ONESHOT_SOURCE
return source or platform or "cli"
def is_attended(agent: Any) -> bool:
"""A delegated child answers its parent, not a person, though its copied context still carries
the parent's source; a library caller (no platform) has no one reading either."""
platform = getattr(agent, "platform", None)
if not platform or platform == "subagent":
return False
# A gateway-admitted ``hermes chat -q`` / ``-z`` turn carries its one-shot fact on the admission,
# not in HERMES_SINGLE_QUERY_SESSION (the daemon never had that env).
from gateway.session_finite import finite_turn_required
if finite_turn_required() is True:
return False
return session_source_for(platform) in ATTENDED_SOURCES