Files
unsupportedpastels d0b49116ec feat(gateway): a client can name itself on session.create
A third-party client can now say who it is: session.create takes an optional
client {name, version?}. It is attribution only. source still picks the
policy, and no toolset, approval, permission, prompt text or telemetry reads
the label.

- Validated beside the editor check (bounded, safe charset); a malformed value
  is invalid_params on field client.
- Kept inside the frozen creation request, so there is no new column, receipt
  key or policy field: an older owner restores the session, and the label is
  part of the exact-retry identity.
- session.info.client echoes it (absent when none was sent); runtime.describe
  lists client in session_create.parameters and capability session-client-v1.
- Plugins read it with hermes_cli.plugin_session.session_client() (published
  in plugin_api.json) and the client / client_version keys of prompt-section
  session info ("" when none). No PluginContext method, no hook kwarg.
- The classic CLI's /new copies the frozen launch request but never the
  creating client's label: the new session belongs to the terminal.
- Contracts regenerated; docs say the label is self-declared and must never
  grant anything.
2026-11-06 20:48:57 -05:00

62 lines
2.8 KiB
Python

"""The session a plugin hook or tool is running for, read from the turn's task-local context.
Plugin hooks and tools run in the gateway process, whose own cwd is the install root and is shared
by every session (it is never ``os.chdir``-ed). These read the calling session's key, client surface
and recorded working directory instead.
"""
from __future__ import annotations
import os
def _active_policy():
from gateway.session_policy import active_policy
return active_policy()
def session_key() -> str:
"""Route key of the session whose turn is running in this context, ``""`` outside one."""
from gateway.session_context import get_session_env
return get_session_env("HERMES_SESSION_KEY", "")
def session_surface() -> str:
"""``cli``/``tui``/``desktop``/``acp`` for a local session's turn, the messaging platform
(``telegram``, ``discord``, ...) for a chat's turn, ``""`` outside any session."""
policy = _active_policy()
if policy is not None:
return policy.platform
from gateway.session_context import get_session_env
return get_session_env("HERMES_SESSION_PLATFORM", "")
def session_client() -> dict | None:
"""The client that created the calling local session, as it declared itself on ``session.create``:
a copy of ``{"name": ..., "version": ...}`` (``version`` only when sent). ``None`` outside a local
session, for a messaging chat, and for a client that sent no ``client`` (Ink, Desktop, the CLI and
ACP today). A label the client chose, not an authentication: never grant anything on it.
Under ``plugins.isolation: host`` the plugin's own process has no session context; read
``info["client"]`` in a prompt section there."""
from gateway.session_client import policy_client
return policy_client(_active_policy())
def session_cwd() -> str:
"""The calling session's recorded working directory (a local session's workspace, else the
profile's terminal cwd), ``""`` when it has none. Never the gateway process's own cwd.
In ingress hooks (``pre_gateway_dispatch``), which run before any session, it is the profile's
terminal cwd, not the workspace of the session the message will reach."""
from agent.runtime_cwd import scope_terminal_cwd, scoped_session_cwd
policy = _active_policy()
return scoped_session_cwd() or (policy.cwd if policy is not None else "") or scope_terminal_cwd().strip()
def context_surface(manager) -> str:
"""``session_surface()`` for a plugin context: the classic CLI process itself is ``cli``."""
return "cli" if manager._cli_ref is not None else session_surface()
def context_cwd(manager) -> str:
"""``session_cwd()`` for a plugin context: the classic CLI process keeps its launch directory."""
return os.getcwd() if manager._cli_ref is not None else session_cwd()