Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions rock/actions/sandbox/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ class CreateBashSessionRequest(BaseModel):
env: dict[str, str] | None = Field(default=None)
remote_user: str | None = Field(default=None)

# Terminal settings
term: str | None = Field(default=None)
"""Terminal type (TERM environment variable). If None, TERM is not set."""

columns: int = Field(default=80, ge=1)
"""Terminal width in columns. Must be positive."""

lines: int = Field(default=24, ge=1)
"""Terminal height in lines. Must be positive."""

lang: str | None = Field(default=None)
"""Language and encoding (LANG environment variable). If None, LANG is not set."""


CreateSessionRequest = Annotated[CreateBashSessionRequest, Field(discriminator="session_type")]
"""Union type for all create session requests. Do not use this directly."""
Expand Down
8 changes: 8 additions & 0 deletions rock/rocklet/local_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ async def start(self) -> CreateBashSessionResponse:
else:
env = {}
env.update({"PS1": self._ps1, "PS2": "", "PS0": ""})

# Set terminal environment variables only if specified
if self.request.term is not None:
env["TERM"] = self.request.term
if self.request.lang is not None:
env["LANG"] = self.request.lang

if self.request.env is not None:
env.update(self.request.env)
logger.info(f"env:{env}")
Expand All @@ -190,6 +197,7 @@ async def start(self) -> CreateBashSessionResponse:
echo=False,
env=env, # type: ignore
maxread=self.request.max_read_size,
dimensions=(self.request.lines, self.request.columns),
)
time.sleep(0.3)
cmds = []
Expand Down
117 changes: 117 additions & 0 deletions tests/unit/rocklet/test_local_sandbox_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,120 @@ async def test_prompt_command(local_runtime: LocalSandboxRuntime):
with_prompt_command = await local_runtime.run_in_session(BashAction(command="echo hello", action_type="bash"))
assert with_prompt_command.output.__contains__("ROCK")
await local_runtime.close_session(CloseBashSessionRequest(session_type="bash"))


# ========== Terminal Settings Tests ==========


@pytest.mark.asyncio
async def test_default_terminal_settings(local_runtime: LocalSandboxRuntime):
"""Test that default terminal settings do not explicitly set TERM/LANG (backward compatibility).

Note: bash/pexpect may still set TERM=dumb by default, but we don't explicitly set it.
The key test is that we can explicitly override TERM/LANG when needed.
"""
import uuid
session_name = f"term_default_{uuid.uuid4().hex[:8]}"
await local_runtime.create_session(
CreateBashSessionRequest(session_type="bash", session=session_name, startup_timeout=5.0)
)

# Check TERM - pexpect/bash defaults to "dumb" when not explicitly set
obs = await local_runtime.run_in_session(
BashAction(command="echo $TERM", action_type="bash", session=session_name, timeout=10)
)
# pexpect/bash defaults to "dumb" when TERM is not set
assert "dumb" in obs.output

# Check LANG - should not be set by default (empty or system default)
obs = await local_runtime.run_in_session(
BashAction(command="echo $LANG", action_type="bash", session=session_name, timeout=10)
)
# LANG may be empty or set to system default; we just verify we don't explicitly set it

await local_runtime.close_session(CloseBashSessionRequest(session_type="bash", session=session_name))


@pytest.mark.asyncio
async def test_custom_terminal_settings(local_runtime: LocalSandboxRuntime):
"""Test that custom terminal settings are applied correctly."""
import uuid
session_name = f"term_custom_{uuid.uuid4().hex[:8]}"
await local_runtime.create_session(
CreateBashSessionRequest(
session_type="bash",
session=session_name,
startup_timeout=5.0,
term="screen",
columns=120,
lines=40,
lang="zh_CN.UTF-8",
)
)

# Check TERM
obs = await local_runtime.run_in_session(
BashAction(command="echo $TERM", action_type="bash", session=session_name, timeout=10)
)
assert "screen" in obs.output

# Check LANG
obs = await local_runtime.run_in_session(
BashAction(command="echo $LANG", action_type="bash", session=session_name, timeout=10)
)
assert "zh_CN.UTF-8" in obs.output

await local_runtime.close_session(CloseBashSessionRequest(session_type="bash", session=session_name))


@pytest.mark.asyncio
async def test_terminal_size_stty(local_runtime: LocalSandboxRuntime):
"""Test that terminal size is correctly set via stty size."""
import uuid
session_name = f"term_stty_{uuid.uuid4().hex[:8]}"
await local_runtime.create_session(
CreateBashSessionRequest(
session_type="bash",
session=session_name,
startup_timeout=5.0,
columns=100,
lines=30,
)
)

# stty size outputs "lines columns"
obs = await local_runtime.run_in_session(
BashAction(command="stty size", action_type="bash", session=session_name, timeout=10)
)
assert "30 100" in obs.output

await local_runtime.close_session(CloseBashSessionRequest(session_type="bash", session=session_name))


@pytest.mark.asyncio
async def test_env_overrides_terminal_params(local_runtime: LocalSandboxRuntime):
"""Test that env parameter takes priority over terminal params."""
import uuid
session_name = f"term_override_{uuid.uuid4().hex[:8]}"
await local_runtime.create_session(
CreateBashSessionRequest(
session_type="bash",
session=session_name,
startup_timeout=5.0,
term="xterm",
env={"TERM": "vt100", "LANG": "C"},
)
)

# env should override term param
obs = await local_runtime.run_in_session(
BashAction(command="echo $TERM", action_type="bash", session=session_name, timeout=10)
)
assert "vt100" in obs.output

obs = await local_runtime.run_in_session(
BashAction(command="echo $LANG", action_type="bash", session=session_name, timeout=10)
)
assert "C" in obs.output

await local_runtime.close_session(CloseBashSessionRequest(session_type="bash", session=session_name))
Loading