Skip to content

Commit 219e4a5

Browse files
authored
default tmux action (#322)
1 parent 68b146e commit 219e4a5

2 files changed

Lines changed: 73 additions & 16 deletions

File tree

lagent/actions/tmux_action.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,32 @@
3737
_TIMEOUT_TEMPLATE = "Command '{command}' timed out after {timeout_sec}s.\n\n{terminal_state}"
3838

3939

40+
TOOLSCHEMA = {
41+
"name": "bash_command",
42+
"description": """Send keystrokes to the persistent tmux pane and return pane output.The pane is a real bash shell running in a pty: it only executes a command once the keystrokes include a line terminator. Each call's keystrokes are sent verbatim; bash accumulates input across calls until it sees '\n' or 'Enter' — if you forget the terminator the command will sit unexecuted in the prompt and get concatenated with whatever you send next. Tmux-style key names are also accepted as tokens: 'C-c' (Ctrl+C), 'C-d' (Ctrl+D), 'Enter', 'Tab'.""",
43+
"parameters": [
44+
{
45+
"name": "keystrokes",
46+
"type": "string",
47+
"description": """exact characters to send. MUST end with '\n' (or 'Enter') for the command to actually execute — without it bash stays in "typing" state and you will only see the prompt echo back your input, not the command's output.""",
48+
},
49+
{
50+
"name": "duration",
51+
"type": "number",
52+
"description": """seconds to wait after sending before reading pane output (default 1.0, max 60.0). A too-short duration is the other way this tool appears to "do nothing": the command is still running when we capture the pane, so you see the prompt without output and assume it failed.
53+
Guidance:
54+
- 1.0 for typical commands (ls, cat, cd, pip install,
55+
python scripts that finish quickly)
56+
- 3-10 for builds / installers / downloads
57+
- up to 60 for very slow commands
58+
If output is incomplete, re-call with ``keystrokes=""`` and a longer ``duration`` to poll further — do NOT resend the command.""",
59+
"default": 1.0,
60+
},
61+
],
62+
"required": ["keystrokes", "duration"],
63+
}
64+
65+
4066
class TmuxSession:
4167
"""Persistent tmux pane driven by local subprocess calls."""
4268

@@ -320,7 +346,7 @@ def __init__(
320346
pane_height: int = 40,
321347
working_dir: str | None = None,
322348
extra_env: dict[str, str] | None = None,
323-
description: dict | None = None,
349+
description: dict = TOOLSCHEMA,
324350
):
325351
"""Create the tool and start the tmux session immediately.
326352
@@ -349,22 +375,17 @@ def session(self) -> TmuxSession:
349375
async def run(self, keystrokes: str, duration: float = 1.0) -> ActionReturn:
350376
"""Send keystrokes to the persistent tmux pane and return pane output.
351377
352-
Each call's keystrokes are sent verbatim to the terminal. Write them
353-
exactly as you want them typed:
354-
- End every command with '\\n' or it will not execute.
355-
- Tmux-style escape sequences are accepted: 'C-c' (Ctrl+C),
356-
'C-d' (Ctrl+D), 'Enter', 'Tab'.
378+
The pane is a real bash shell running in a pty: it only executes a command once the keystrokes include a line terminator. Each call's keystrokes are sent verbatim; bash accumulates input across calls until it sees '\n' or 'Enter' — if you forget the terminator the command will sit unexecuted in the prompt and get concatenated with whatever you send next. Tmux-style key names are also accepted as tokens: 'C-c' (Ctrl+C), 'C-d' (Ctrl+D), 'Enter', 'Tab'.
357379
358380
Args:
359-
keystrokes (str): exact characters to send to the terminal.
360-
duration (float): seconds to wait after sending before returning
361-
output (default 1.0, max 60.0). Use 0.1 for immediate
362-
commands (cd, ls, echo, cat), 1.0 for typical commands
363-
(gcc, find, rustc), longer for slow commands (make, wget,
364-
long-running scripts). Prefer polling with an empty
365-
keystrokes string over long single waits — call again with
366-
``keystrokes=""`` and a longer duration if output is not
367-
yet complete. Never wait longer than 60 seconds.
381+
keystrokes (str): exact characters to send. MUST end with '\n' (or 'Enter') for the command to actually execute — without it bash stays in "typing" state and you will only see the prompt echo back your input, not the command's output.
382+
duration (float): seconds to wait after sending before reading pane output (default 1.0, max 60.0). A too-short duration is the other way this tool appears to "do nothing": the command is still running when we capture the pane, so you see the prompt without output and assume it failed.
383+
Guidance:
384+
- 1.0 for typical commands (ls, cat, cd, pip install,
385+
python scripts that finish quickly)
386+
- 3-10 for builds / installers / downloads
387+
- up to 60 for very slow commands
388+
If output is incomplete, re-call with ``keystrokes=""`` and a longer ``duration`` to poll further — do NOT resend the command.
368389
369390
Returns:
370391
str: terminal output (new pane content since the last call, or
@@ -376,6 +397,25 @@ async def run(self, keystrokes: str, duration: float = 1.0) -> ActionReturn:
376397
errmsg="`keystrokes` must be a string.",
377398
state=ActionStatusCode.ARGS_ERROR,
378399
)
400+
# Fast-fail on malformed input so RL gets a crisp reward signal.
401+
# Empty string is allowed (polling pane without sending anything).
402+
# Non-empty keystrokes MUST end with '\\n'/'\\r' or be a pure tmux
403+
# key name like 'Enter' / 'C-m' / 'C-c' — otherwise bash will not
404+
# execute and the pane will just echo characters.
405+
# if keystrokes and not TmuxSession._is_executing_command(keystrokes):
406+
# return ActionReturn(
407+
# type=self.name,
408+
# errmsg=(
409+
# r"keystrokes must end with '\n' (or 'Enter') for bash to "
410+
# "actually execute the command. Without a line terminator "
411+
# "the characters accumulate in the shell prompt and do "
412+
# "nothing — you will only see your own keystrokes echoed "
413+
# "back, not the command output. Use ``keystrokes=\"\"`` "
414+
# "only when polling for more output from a previously "
415+
# "submitted command."
416+
# ),
417+
# state=ActionStatusCode.ARGS_ERROR,
418+
# )
379419
try:
380420
duration = min(float(duration), _DEFAULT_DURATION_CAP_SEC)
381421
except (TypeError, ValueError):

lagent/hooks/logger.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,26 @@ def _process_message(self, message):
3232
msg_str = f'message sender: {sender}'
3333
if getattr(message, 'reasoning_content', None):
3434
msg_str += f'\nReasoning:{message.reasoning_content}'
35+
thinking = getattr(message, 'thinking', None)
36+
if thinking:
37+
msg_str += f'\nThinking:{thinking}'
3538
if getattr(message, 'content', None):
3639
msg_str += f'\nContent:{message.content}'
3740
if getattr(message, 'tool_calls', None):
3841
msg_str += f'\nTool Calls:{message.tool_calls}'
39-
42+
raw_content = getattr(message, 'raw_content', None)
43+
if raw_content and raw_content != getattr(message, 'content', None):
44+
msg_str += f'\nRaw:{raw_content}'
45+
if getattr(message, 'reward', None) is not None:
46+
msg_str += f'\nReward:{message.reward}'
47+
finish_reason = getattr(message, 'finish_reason', None)
48+
if finish_reason:
49+
msg_str += f'\nFinishReason:{finish_reason}'
50+
stream_state = getattr(message, 'stream_state', None)
51+
if stream_state is not None:
52+
msg_str += f'\nStreamState:{stream_state}'
53+
extra_info = getattr(message, 'extra_info', None)
54+
if extra_info:
55+
msg_str += f'\nExtraInfo:{extra_info}'
56+
4057
self.logger.info(colored(msg_str, color))

0 commit comments

Comments
 (0)