Files
hermes-agent/tools/process_registry_stdin.py
Teknium a56aa010cc fix(tools): already-exited stdin advice no longer claims every process read stdin
A pipe-mode process that never touched stdin did not "get end-of-file"; the
message now says "if it read stdin, it got end-of-file at once".
2026-10-28 13:10:03 -07:00

51 lines
2.7 KiB
Python

"""Why a background process has no writable stdin, phrased as the fix the agent can act on."""
import importlib.util
import sys
_NON_INTERACTIVE = "Pass input non-interactively instead (arguments, a here-doc, or `printf ... | cmd`)."
def _pty_available() -> bool:
return importlib.util.find_spec("winpty" if sys.platform == "win32" else "ptyprocess") is not None
def _no_pty_reason(session) -> str:
"""Why a pty=true relaunch of this local pipe-mode session would not help, or "" when it would.
``spawn_local`` records a pty=true request that fell back to pipe mode, so the advice never
repeats a request that already failed: an installed PTY library is not proof a PTY spawns."""
if session.pty_fallback:
return f"pty=true was requested but {session.pty_fallback}, so it runs without stdin"
if session.output_log:
return "background processes here run in an isolated worker that cannot give them a pseudo-terminal"
if not _pty_available():
return "the PTY library (ptyprocess/pywinpty) is not installed"
return ""
def already_exited_error(session) -> str:
"""An exited pipe-mode process had no stdin; if it read stdin it got EOF at once. Say so, or the agent
retries blind."""
if session.process is None or session.process.stdin is not None:
return "Process has already finished"
if reason := _no_pty_reason(session):
return (f"Process has already finished. It ran without stdin ({reason}), so if it read stdin, it "
f"got end-of-file at once. {_NON_INTERACTIVE}")
return ("Process has already finished. It was started without stdin (terminal background=true without "
"pty=true), so if it read stdin, it got end-of-file at once. Relaunch it with "
"terminal(background=true, pty=true) to send input.")
def stdin_unavailable_error(session) -> str:
"""Error for process_manage write/submit on a session without a stdin pipe or PTY.
A local pipe-mode spawn connects stdin to /dev/null by design, so only a PTY relaunch can take
input, and only where ``spawn_local`` can give one; a non-local backend has no live stdin."""
if session.process is None:
return ("Process stdin not available: it runs on a non-local terminal backend (or was recovered "
f"after a restart), which has no live stdin. {_NON_INTERACTIVE}")
if reason := _no_pty_reason(session):
return f"This process has no stdin: {reason}. Relaunching with pty=true would not help. {_NON_INTERACTIVE}"
return ("This process was started without stdin (terminal background=true without pty=true). "
"Relaunch it with terminal(background=true, pty=true) to send input.")