Files

231 lines
7.9 KiB
Python

"""Server→client requests: the backend asks the renderer a question (``server_requests.send``).
Every entry is one request method: the ``params`` the frame carries (``session_id`` is added by
the transport and declared on the shared base) and the ``result`` the client answers with. The
``request.cancel`` event that withdraws an open request lives here too.
"""
from __future__ import annotations
from pydantic import Field
from .base import JsonValue, Params, Payload, Result, WireEnum
from .registry import event, server_request
class ServerRequestParams(Params):
"""Every request frame's params start with the session the question belongs to."""
session_id: str
class ValueResult(Result):
"""The answer to any one-string prompt (sudo, secret, vault prompts, desktop bridges):
``''`` means skipped / declined."""
value: str
# ── clarify ───────────────────────────────────────────────────────────────────────────────────
class ClarifyQuestion(Params):
qid: str
question: str
choices: list[str] | None = None
multi_select: bool = False
class ClarifyRequestParams(ServerRequestParams):
"""Single question: ``question`` / ``choices`` (/ ``multi_select``); batch: ``questions``.
``answers`` rides only on a reconnect replay (locks the server already accepted)."""
question: str | None = None
choices: list[str] | None = None
multi_select: bool | None = None
questions: list[ClarifyQuestion] | None = None
answers: dict[str, str] | None = None
class ClarifyResult(Result):
"""Single: ``{answer}`` ('' = skip). Batch: ``{answers}`` for the whole set (early locks go through
the ``clarify.lock`` RPC); a response with neither is cancel-all."""
answer: str | None = None
answers: dict[str, str] | None = None
server_request("clarify", params=ClarifyRequestParams, result=ClarifyResult,
doc="The clarify tool: ask the user one question or a batch.")
# ── approval ──────────────────────────────────────────────────────────────────────────────────
class ApprovalChoice(WireEnum):
once = "once"
session = "session"
always = "always"
deny = "deny"
class ApprovalRequestParams(ServerRequestParams):
"""``tui_gateway/server.py::_approval_request_payload`` — the command is redacted server-side."""
request_id: str
command: str = ""
description: str = ""
choices: list[ApprovalChoice] = Field(default_factory=list)
allow_permanent: bool | None = None
allow_session: bool | None = None
smart_denied: bool | None = None
tool_name: str | None = None
session_id_hint: str | None = Field(default=None, alias="gateway_session_id")
# The approval queue entry carries tool-specific context the card may render; the closed set
# of keys is owned by tools/approval.py, so it stays open here.
model_config = Params.model_config | {"extra": "allow"}
class ApprovalResult(Result):
choice: ApprovalChoice
all: bool | None = None
server_request("approval", params=ApprovalRequestParams, result=ApprovalResult,
doc="A dangerous command awaits the user's decision.")
# ── one-string prompts ────────────────────────────────────────────────────────────────────────
class EmptyRequestParams(ServerRequestParams):
pass
class SudoRequestParams(ServerRequestParams):
"""Original command, redacted server-side before any password-injection rewrite."""
command: str = ""
server_request("sudo", params=SudoRequestParams, result=ValueResult,
doc="Masked sudo password for the terminal tool.")
class SecretRequestParams(ServerRequestParams):
env_var: str
prompt: str
metadata: dict[str, JsonValue] | None = None
server_request("secret", params=SecretRequestParams, result=ValueResult,
doc="Masked value for a named env var (skills / setup flows).")
class VaultUnlockRequestParams(ServerRequestParams):
backend: str
display_name: str
server_request("vault.unlock_prompt", params=VaultUnlockRequestParams, result=ValueResult,
doc="Master password to unlock an external password manager for this session.")
class VaultSaveLoginRequestParams(ServerRequestParams):
origin: str
site: str
server_request("vault.save_login", params=VaultSaveLoginRequestParams, result=ValueResult,
doc="Save a login for a site the agent is about to fill; the answer is JSON {identifier, password}.")
class VaultCodeRequestParams(ServerRequestParams):
site: str | None = None
hint: str | None = None
server_request("vault.code", params=VaultCodeRequestParams, result=ValueResult,
doc="A one-time / 2FA code the user reads from their device.")
# ── desktop GUI bridges ───────────────────────────────────────────────────────────────────────
class ReadRangeRequestParams(ServerRequestParams):
start: int | None = None
count: int | None = None
server_request("terminal.read", params=ReadRangeRequestParams, result=ValueResult,
doc="Read the visible in-app terminal buffer (JSON text answer).")
server_request("preview.read", params=ReadRangeRequestParams, result=ValueResult,
doc="Read the in-app browser preview's text (JSON text answer).")
server_request("window.read", params=EmptyRequestParams, result=ValueResult,
doc="Enumerate the native window below the app (JSON text answer).")
class PreviewActRequestParams(ServerRequestParams):
"""``tools/drive_preview_tool.py`` and ``tools/annotate_preview_tool.py`` field sets."""
action: str
ref: str | None = None
selector: str | None = None
text: str | None = None
key: str | None = None
submit: bool | None = None
full: bool | None = None
to: str | None = None
amount: int | None = None
max: int | None = None
server_request("preview.act", params=PreviewActRequestParams, result=ValueResult,
doc="Click / type / scroll / annotate inside the in-app browser preview.")
class TourStep(Params):
selector: str | None = None
title: str | None = None
text: str | None = None
side: str | None = None
model_config = Params.model_config | {"extra": "allow"}
class TourRequestParams(ServerRequestParams):
"""``tools/tour_tool.py`` field set."""
action: str
surface: str | None = None
selector: str | None = None
title: str | None = None
text: str | None = None
side: str | None = None
steps: list[TourStep] | None = None
step_index: int | None = None
server_request("tour", params=TourRequestParams, result=ValueResult,
doc="Drive a guided tour highlight in the desktop renderer.")
# ── withdrawal ────────────────────────────────────────────────────────────────────────────────
class RequestCancelReason(WireEnum):
timeout = "timeout"
interrupted = "interrupted"
shutdown = "shutdown"
resolved = "resolved"
session_closed = "session_closed"
class RequestCancelPayload(Payload):
id: str
method: str
reason: str # a RequestCancelReason value; callers in tools/approval may pass their own wording
event("request.cancel", RequestCancelPayload,
doc="The backend withdrew an open server→client request; clear the matching card only.")