231 lines
7.9 KiB
Python
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.")
|