try to fix terminal for linux

This commit is contained in:
Concedo 2026-09-24 20:23:54 +08:00
parent cbdff82e20
commit 3c32dea22c
2 changed files with 52 additions and 7 deletions

View file

@ -41,6 +41,12 @@ import urllib.request
from pathlib import Path
from typing import Any, Callable
if os.name != "nt":
try:
import readline # Enable standard line editing and in-memory history.
except ImportError:
pass # Optional in some Python builds; plain input() still works.
DEFAULT_MAX_TOOL_RESULT_CHARS = 20000
MAX_TOOL_RESULT_CHARS = DEFAULT_MAX_TOOL_RESULT_CHARS
@ -195,6 +201,14 @@ def color(text: str, code: str, *, stderr: bool = False) -> str:
return f"{code}{text}{ANSI_RESET}" if enabled else text
def input_prompt(text: str) -> str:
label = color(text, ANSI_BOLD_CYAN)
if os.name != "nt" and "readline" in sys.modules:
# Readline must exclude ANSI color sequences when counting columns.
label = re.sub(r"\x1b\[[0-9;]*m", lambda match: "\001" + match[0] + "\002", label)
return label + " "
def toggle_status(enabled: bool) -> str:
return color("ON" if enabled else "OFF", ANSI_GREEN if enabled else ANSI_YELLOW)
@ -748,6 +762,16 @@ def tool_shell(args: dict[str, Any]) -> str:
raise RuntimeError("No POSIX command shell was found")
argv = [executable, "-c", command]
shell_env = None
if os.name == "posix" and getattr(sys, "frozen", False):
# System commands must not load the frozen agent's bundled libraries.
shell_env = os.environ.copy()
original_library_path = shell_env.get("LD_LIBRARY_PATH_ORIG")
if original_library_path is not None:
shell_env["LD_LIBRARY_PATH"] = original_library_path
else:
shell_env.pop("LD_LIBRARY_PATH", None)
completed = subprocess.run(
argv,
capture_output=True,
@ -755,6 +779,7 @@ def tool_shell(args: dict[str, Any]) -> str:
encoding="utf-8",
errors="replace",
timeout=timeout,
env=shell_env,
)
return limit_text(
@ -777,7 +802,7 @@ def tool_ask_user(args: dict[str, Any]) -> str:
raise ValueError("question must be a non-empty string")
print("\n" + color("Agent asks:", ANSI_CYAN) + f" {question.strip()}")
try:
answer = input(color("Your answer>", ANSI_BOLD_CYAN) + " ")
answer = input(input_prompt("Your answer>"))
except (EOFError, KeyboardInterrupt):
print()
return "The user declined to answer."
@ -1735,7 +1760,7 @@ def run_agent(
while True:
try:
user_text = input(color("User>", ANSI_BOLD_CYAN) + " ").strip()
user_text = input(input_prompt("User>")).strip()
except (EOFError, KeyboardInterrupt):
print("\nExiting.")
return

View file

@ -11700,7 +11700,8 @@ def run_bundled_kobold_agent(base_url=None, api_key=None):
if not os.path.isfile(agent_path):
raise FileNotFoundError(f"Kobold Agent script not found: {agent_path}")
import runpy
# A normal import also lets PyInstaller collect the agent's stdlib imports.
import kcpp_agent
old_argv = sys.argv
try:
sys.argv = [agent_path]
@ -11708,7 +11709,7 @@ def run_bundled_kobold_agent(base_url=None, api_key=None):
sys.argv.extend(["--base-url", base_url])
if api_key:
sys.argv.extend(["--api-key", api_key])
runpy.run_path(agent_path, run_name="__main__")
kcpp_agent.main()
finally:
sys.argv = old_argv
@ -11754,6 +11755,23 @@ def launch_kobold_agent_terminal(base_url=None, api_key=None):
apple_script = f'tell application "Terminal" to do script {json.dumps(shell_command)}'
subprocess.Popen(["osascript", "-e", apple_script], start_new_session=True)
else:
# Frozen Linux builds prepend their extraction directory to
# LD_LIBRARY_PATH. Do not leak those bundled libraries into a
# system terminal emulator: mixing them with the terminal's system
# libraries can produce loader errors (for example, Cairo failing
# to resolve FreeType symbols) before the agent is ever started.
terminal_env = os.environ.copy()
if is_frozen:
original_library_path = terminal_env.get("LD_LIBRARY_PATH_ORIG")
if original_library_path is not None:
terminal_env["LD_LIBRARY_PATH"] = original_library_path
else:
terminal_env.pop("LD_LIBRARY_PATH", None)
# Give the agent its own extracted files, even if this launcher
# exits. Set this on the command so terminal-server handoffs
# cannot lose it when forwarding to an existing terminal.
command = ["env", "PYINSTALLER_RESET_ENVIRONMENT=1", *command]
terminal_commands = [
("x-terminal-emulator", ["-e"]),
("gnome-terminal", ["--"]),
@ -11774,13 +11792,13 @@ def launch_kobold_agent_terminal(base_url=None, api_key=None):
if resolved_path in attempted:
continue
attempted.add(resolved_path)
# xterm's default bitmap font is often absent in minimal WSL
# xterm's default bitmap font is often absent in minimal Linux
# installations; request a fontconfig-backed font instead.
launch_args = (["-fa", "monospace"] if os.path.basename(resolved_path) == "xterm" else []) + terminal_args
try:
terminal_process = subprocess.Popen(
[terminal_path, *launch_args, *command],
cwd=os.getcwd(), start_new_session=True)
cwd=os.getcwd(), env=terminal_env, start_new_session=True)
try:
exit_code = terminal_process.wait(timeout=1)
except subprocess.TimeoutExpired:
@ -11794,7 +11812,9 @@ def launch_kobold_agent_terminal(base_url=None, api_key=None):
print(f"Cannot launch Kobold Agent: {'; '.join(failures)}.")
else:
print("Cannot launch Kobold Agent: no supported terminal emulator was found.")
print("Try running kcpp_agent.py in another terminal window.")
manual_command = ([sys.executable, "--run-bundled-agent"] if is_frozen
else [sys.executable, agent_path])
print(f"Try running this in an existing terminal: {shlex.join(manual_command)}")
return False
return True
except Exception as e: