4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
"""Prompt input and stdin coordination policy for runtime turns."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from surfaces.interactive_shell.session import Session
|
|
from surfaces.interactive_shell.ui.components.choice_menu import repl_tty_interactive
|
|
|
|
|
|
def _literal_slash_command_text(text: str) -> str | None:
|
|
"""Return literal ``/slash`` command text for command-shaped input, else ``None``.
|
|
|
|
Terminal-UI policy only (spinner suppression and exclusive-stdin gating). The
|
|
matching execution-side deterministic dispatch lives separately in
|
|
``core/agent_harness/turns/action_driver.py``; keep this function UI-only and do not
|
|
grow natural-language intent inference here.
|
|
"""
|
|
stripped = text.strip()
|
|
return stripped if stripped.startswith("/") else None
|
|
|
|
|
|
_EXCLUSIVE_STDIN_MENU_COMMANDS: frozenset[str] = frozenset(
|
|
{
|
|
"/history",
|
|
"/auth",
|
|
"/help",
|
|
"/integrations",
|
|
"/investigate",
|
|
"/mcp",
|
|
"/model",
|
|
"/tools",
|
|
"/template",
|
|
"/trust",
|
|
"/verbose",
|
|
"/?",
|
|
# Table-outputting commands must complete before the next prompt_async()
|
|
# starts, otherwise patch_stdout redraws trigger ESC[6n DSR queries whose
|
|
# CPR responses land as literal keystrokes in the incoming prompt buffer.
|
|
"/doctor",
|
|
"/version",
|
|
"/verify",
|
|
"/status",
|
|
"/cost",
|
|
"/tasks",
|
|
"/watches",
|
|
"/alerts",
|
|
"/privacy",
|
|
"/context",
|
|
"/fleet",
|
|
"/compact",
|
|
"/welcome",
|
|
"/sessions",
|
|
"/resume",
|
|
"/new",
|
|
"/rca",
|
|
}
|
|
)
|
|
_EXCLUSIVE_STDIN_SUBCOMMANDS: frozenset[tuple[str, str]] = frozenset(
|
|
{
|
|
("/integrations", "setup"),
|
|
# ``remove`` drives a native inline arrow-key picker (raw os.read on
|
|
# stdin). Without exclusive stdin the concurrent prompt_async() steals
|
|
# keystrokes and CPR responses leak into the next prompt buffer.
|
|
("/integrations", "remove"),
|
|
("/mcp", "connect"),
|
|
("/mcp", "disconnect"),
|
|
("/rca", "history"),
|
|
("/rca", "list"),
|
|
("/rca", "ls"),
|
|
("/rca", "show"),
|
|
("/rca", "save"),
|
|
}
|
|
)
|
|
_WAIT_FOR_COMPLETION_COMMANDS: frozenset[str] = frozenset(
|
|
{"/exit", "/quit", "/update", "/onboard", "/config", "/auth", "/login"}
|
|
)
|
|
|
|
|
|
def turn_should_show_spinner(text: str, _session: Session) -> bool:
|
|
# UI-only: suppress the "thinking" spinner for literal slash commands, which
|
|
# dispatch deterministically (no LLM) and would otherwise show a misleading
|
|
# spinner. Natural-language turns still go through the action-agent LLM.
|
|
return _literal_slash_command_text(text.strip()) is None
|
|
|
|
|
|
def turn_needs_exclusive_stdin(text: str, _session: Session) -> bool:
|
|
if not repl_tty_interactive():
|
|
return False
|
|
|
|
t = text.strip()
|
|
if not t:
|
|
return False
|
|
|
|
# Reserve stdin early for literal command-shaped input, but do not dispatch
|
|
# here. This stays UI-only; deterministic slash execution lives in the turn
|
|
# engine (core/agent_harness/turns/action_driver.py), not in this gating layer.
|
|
dispatch_text = _literal_slash_command_text(t)
|
|
if dispatch_text is None:
|
|
return False
|
|
|
|
parts = dispatch_text.split()
|
|
if not parts:
|
|
return False
|
|
name = parts[0].lower()
|
|
args = [arg.lower() for arg in parts[1:]]
|
|
|
|
if name in _WAIT_FOR_COMPLETION_COMMANDS:
|
|
return True
|
|
if name == "/theme":
|
|
return True
|
|
if name in _EXCLUSIVE_STDIN_MENU_COMMANDS and not args:
|
|
return True
|
|
if name == "/tests" and not args:
|
|
return True
|
|
return bool(args and (name, args[0]) in _EXCLUSIVE_STDIN_SUBCOMMANDS)
|
|
|
|
|
|
__all__ = [
|
|
"turn_needs_exclusive_stdin",
|
|
"turn_should_show_spinner",
|
|
]
|