strix-agent 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
1
5
  import logging
2
6
  import os
7
+ import signal
8
+ import sys
9
+ from multiprocessing import Process, Queue
3
10
  from typing import Any
4
11
 
12
+ import uvicorn
5
13
  from fastapi import Depends, FastAPI, HTTPException, status
6
14
  from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
7
15
  from pydantic import BaseModel, ValidationError
@@ -11,20 +19,25 @@ SANDBOX_MODE = os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"
11
19
  if not SANDBOX_MODE:
12
20
  raise RuntimeError("Tool server should only run in sandbox mode (STRIX_SANDBOX_MODE=true)")
13
21
 
14
- EXPECTED_TOKEN = os.getenv("STRIX_SANDBOX_TOKEN")
15
- if not EXPECTED_TOKEN:
16
- raise RuntimeError("STRIX_SANDBOX_TOKEN environment variable is required in sandbox mode")
22
+ parser = argparse.ArgumentParser(description="Start Strix tool server")
23
+ parser.add_argument("--token", required=True, help="Authentication token")
24
+ parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") # nosec
25
+ parser.add_argument("--port", type=int, required=True, help="Port to bind to")
26
+
27
+ args = parser.parse_args()
28
+ EXPECTED_TOKEN = args.token
17
29
 
18
30
  app = FastAPI()
19
- logger = logging.getLogger(__name__)
20
31
  security = HTTPBearer()
21
32
 
22
33
  security_dependency = Depends(security)
23
34
 
35
+ agent_processes: dict[str, dict[str, Any]] = {}
36
+ agent_queues: dict[str, dict[str, Queue[Any]]] = {}
37
+
24
38
 
25
39
  def verify_token(credentials: HTTPAuthorizationCredentials) -> str:
26
40
  if not credentials or credentials.scheme != "Bearer":
27
- logger.warning("Authentication failed: Invalid or missing Bearer token scheme")
28
41
  raise HTTPException(
29
42
  status_code=status.HTTP_401_UNAUTHORIZED,
30
43
  detail="Invalid authentication scheme. Bearer token required.",
@@ -32,18 +45,17 @@ def verify_token(credentials: HTTPAuthorizationCredentials) -> str:
32
45
  )
33
46
 
34
47
  if credentials.credentials != EXPECTED_TOKEN:
35
- logger.warning("Authentication failed: Invalid token provided from remote host")
36
48
  raise HTTPException(
37
49
  status_code=status.HTTP_401_UNAUTHORIZED,
38
50
  detail="Invalid authentication token",
39
51
  headers={"WWW-Authenticate": "Bearer"},
40
52
  )
41
53
 
42
- logger.debug("Authentication successful for tool execution request")
43
54
  return credentials.credentials
44
55
 
45
56
 
46
57
  class ToolExecutionRequest(BaseModel):
58
+ agent_id: str
47
59
  tool_name: str
48
60
  kwargs: dict[str, Any]
49
61
 
@@ -53,45 +65,141 @@ class ToolExecutionResponse(BaseModel):
53
65
  error: str | None = None
54
66
 
55
67
 
68
+ def agent_worker(_agent_id: str, request_queue: Queue[Any], response_queue: Queue[Any]) -> None:
69
+ null_handler = logging.NullHandler()
70
+
71
+ root_logger = logging.getLogger()
72
+ root_logger.handlers = [null_handler]
73
+ root_logger.setLevel(logging.CRITICAL)
74
+
75
+ from strix.tools.argument_parser import ArgumentConversionError, convert_arguments
76
+ from strix.tools.registry import get_tool_by_name
77
+
78
+ while True:
79
+ try:
80
+ request = request_queue.get()
81
+
82
+ if request is None:
83
+ break
84
+
85
+ tool_name = request["tool_name"]
86
+ kwargs = request["kwargs"]
87
+
88
+ try:
89
+ tool_func = get_tool_by_name(tool_name)
90
+ if not tool_func:
91
+ response_queue.put({"error": f"Tool '{tool_name}' not found"})
92
+ continue
93
+
94
+ converted_kwargs = convert_arguments(tool_func, kwargs)
95
+ result = tool_func(**converted_kwargs)
96
+
97
+ response_queue.put({"result": result})
98
+
99
+ except (ArgumentConversionError, ValidationError) as e:
100
+ response_queue.put({"error": f"Invalid arguments: {e}"})
101
+ except (RuntimeError, ValueError, ImportError) as e:
102
+ response_queue.put({"error": f"Tool execution error: {e}"})
103
+
104
+ except (RuntimeError, ValueError, ImportError) as e:
105
+ response_queue.put({"error": f"Worker error: {e}"})
106
+
107
+
108
+ def ensure_agent_process(agent_id: str) -> tuple[Queue[Any], Queue[Any]]:
109
+ if agent_id not in agent_processes:
110
+ request_queue: Queue[Any] = Queue()
111
+ response_queue: Queue[Any] = Queue()
112
+
113
+ process = Process(
114
+ target=agent_worker, args=(agent_id, request_queue, response_queue), daemon=True
115
+ )
116
+ process.start()
117
+
118
+ agent_processes[agent_id] = {"process": process, "pid": process.pid}
119
+ agent_queues[agent_id] = {"request": request_queue, "response": response_queue}
120
+
121
+ return agent_queues[agent_id]["request"], agent_queues[agent_id]["response"]
122
+
123
+
56
124
  @app.post("/execute", response_model=ToolExecutionResponse)
57
125
  async def execute_tool(
58
126
  request: ToolExecutionRequest, credentials: HTTPAuthorizationCredentials = security_dependency
59
127
  ) -> ToolExecutionResponse:
60
128
  verify_token(credentials)
61
129
 
62
- from strix.tools.argument_parser import ArgumentConversionError, convert_arguments
63
- from strix.tools.registry import get_tool_by_name
130
+ request_queue, response_queue = ensure_agent_process(request.agent_id)
131
+
132
+ request_queue.put({"tool_name": request.tool_name, "kwargs": request.kwargs})
64
133
 
65
134
  try:
66
- tool_func = get_tool_by_name(request.tool_name)
67
- if not tool_func:
68
- return ToolExecutionResponse(error=f"Tool '{request.tool_name}' not found")
135
+ loop = asyncio.get_event_loop()
136
+ response = await loop.run_in_executor(None, response_queue.get)
69
137
 
70
- converted_kwargs = convert_arguments(tool_func, request.kwargs)
138
+ if "error" in response:
139
+ return ToolExecutionResponse(error=response["error"])
140
+ return ToolExecutionResponse(result=response.get("result"))
71
141
 
72
- result = tool_func(**converted_kwargs)
142
+ except (RuntimeError, ValueError, OSError) as e:
143
+ return ToolExecutionResponse(error=f"Worker error: {e}")
73
144
 
74
- return ToolExecutionResponse(result=result)
75
145
 
76
- except (ArgumentConversionError, ValidationError) as e:
77
- logger.warning("Invalid tool arguments: %s", e)
78
- return ToolExecutionResponse(error=f"Invalid arguments: {e}")
79
- except TypeError as e:
80
- logger.warning("Tool execution type error: %s", e)
81
- return ToolExecutionResponse(error=f"Tool execution error: {e}")
82
- except ValueError as e:
83
- logger.warning("Tool execution value error: %s", e)
84
- return ToolExecutionResponse(error=f"Tool execution error: {e}")
85
- except Exception:
86
- logger.exception("Unexpected error during tool execution")
87
- return ToolExecutionResponse(error="Internal server error")
146
+ @app.post("/register_agent")
147
+ async def register_agent(
148
+ agent_id: str, credentials: HTTPAuthorizationCredentials = security_dependency
149
+ ) -> dict[str, str]:
150
+ verify_token(credentials)
151
+
152
+ ensure_agent_process(agent_id)
153
+ return {"status": "registered", "agent_id": agent_id}
88
154
 
89
155
 
90
156
  @app.get("/health")
91
- async def health_check() -> dict[str, str]:
157
+ async def health_check() -> dict[str, Any]:
92
158
  return {
93
159
  "status": "healthy",
94
160
  "sandbox_mode": str(SANDBOX_MODE),
95
161
  "environment": "sandbox" if SANDBOX_MODE else "main",
96
162
  "auth_configured": "true" if EXPECTED_TOKEN else "false",
163
+ "active_agents": len(agent_processes),
164
+ "agents": list(agent_processes.keys()),
97
165
  }
166
+
167
+
168
+ def cleanup_all_agents() -> None:
169
+ for agent_id in list(agent_processes.keys()):
170
+ try:
171
+ agent_queues[agent_id]["request"].put(None)
172
+ process = agent_processes[agent_id]["process"]
173
+
174
+ process.join(timeout=1)
175
+
176
+ if process.is_alive():
177
+ process.terminate()
178
+ process.join(timeout=1)
179
+
180
+ if process.is_alive():
181
+ process.kill()
182
+
183
+ except (BrokenPipeError, EOFError, OSError):
184
+ pass
185
+ except (RuntimeError, ValueError) as e:
186
+ logging.getLogger(__name__).debug(f"Error during agent cleanup: {e}")
187
+
188
+
189
+ def signal_handler(_signum: int, _frame: Any) -> None:
190
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN) if hasattr(signal, "SIGPIPE") else None
191
+ cleanup_all_agents()
192
+ sys.exit(0)
193
+
194
+
195
+ if hasattr(signal, "SIGPIPE"):
196
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN)
197
+
198
+ signal.signal(signal.SIGTERM, signal_handler)
199
+ signal.signal(signal.SIGINT, signal_handler)
200
+
201
+ if __name__ == "__main__":
202
+ try:
203
+ uvicorn.run(app, host=args.host, port=args.port, log_level="info")
204
+ finally:
205
+ cleanup_all_agents()
@@ -57,10 +57,10 @@ def _run_agent_in_thread(
57
57
  - Work independently with your own approach
58
58
  - Use agent_finish when complete to report back to parent
59
59
  - You are a SPECIALIST for this specific task
60
- - The previous browser, sessions, proxy history, and files in /workspace were for your
61
- parent agent. Do not depend on them.
62
- - You are starting with a fresh context. Fresh proxy, browser, and files.
63
- Only stuff in /shared_workspace is passed to you from context.
60
+ - You share the same container as other agents but have your own tool server instance
61
+ - All agents share /workspace directory and proxy history for better collaboration
62
+ - You can see files created by other agents and proxy traffic from previous work
63
+ - Build upon previous work but focus on your specific delegated task
64
64
  </instructions>
65
65
  </agent_delegation>"""
66
66
 
@@ -80,7 +80,7 @@ Only create a new agent if no existing agent is handling the specific task.</des
80
80
  <description>Whether the new agent should inherit parent's conversation history and context</description>
81
81
  </parameter>
82
82
  <parameter name="prompt_modules" type="string" required="false">
83
- <description>Comma-separated list of prompt modules to use for the agent. Most agents should have at least one module in order to be useful. {{DYNAMIC_MODULES_DESCRIPTION}}</description>
83
+ <description>Comma-separated list of prompt modules to use for the agent (MAXIMUM 3 modules allowed). Most agents should have at least one module in order to be useful. Agents should be highly specialized - use 1-3 related vulnerability modules only. {{DYNAMIC_MODULES_DESCRIPTION}}</description>
84
84
  </parameter>
85
85
  </parameters>
86
86
  <returns type="Dict[str, Any]">
@@ -104,6 +104,22 @@ Only create a new agent if no existing agent is handling the specific task.</des
104
104
  for security vulnerabilities and bypass techniques.</parameter>
105
105
  <parameter=name>Auth Specialist</parameter>
106
106
  <parameter=prompt_modules>authentication_jwt, business_logic</parameter>
107
+ </function>
108
+
109
+ # Example of single-module specialization (most focused)
110
+ <function=create_agent>
111
+ <parameter=task>Perform comprehensive XSS testing including reflected, stored, and DOM-based
112
+ variants across all identified input points.</parameter>
113
+ <parameter=name>XSS Specialist</parameter>
114
+ <parameter=prompt_modules>xss</parameter>
115
+ </function>
116
+
117
+ # Example of maximum 3 related modules (borderline acceptable)
118
+ <function=create_agent>
119
+ <parameter=task>Test for server-side vulnerabilities including SSRF, XXE, and potential
120
+ RCE vectors in file upload and XML processing endpoints.</parameter>
121
+ <parameter=name>Server-Side Attack Specialist</parameter>
122
+ <parameter=prompt_modules>ssrf, xxe, rce</parameter>
107
123
  </function>
108
124
  </examples>
109
125
  </tool>
@@ -1,6 +1,7 @@
1
1
  import contextlib
2
2
  import inspect
3
3
  import json
4
+ import types
4
5
  from collections.abc import Callable
5
6
  from typing import Any, Union, get_args, get_origin
6
7
 
@@ -48,7 +49,7 @@ def convert_arguments(func: Callable[..., Any], kwargs: dict[str, Any]) -> dict[
48
49
 
49
50
  def convert_string_to_type(value: str, param_type: Any) -> Any:
50
51
  origin = get_origin(param_type)
51
- if origin is Union or origin is type(str | None):
52
+ if origin is Union or isinstance(param_type, types.UnionType):
52
53
  args = get_args(param_type)
53
54
  for arg_type in args:
54
55
  if arg_type is not type(None):
strix/tools/executor.py CHANGED
@@ -49,7 +49,10 @@ async def _execute_tool_in_sandbox(tool_name: str, agent_state: Any, **kwargs: A
49
49
  server_url = await runtime.get_sandbox_url(agent_state.sandbox_id, tool_server_port)
50
50
  request_url = f"{server_url}/execute"
51
51
 
52
+ agent_id = getattr(agent_state, "agent_id", "unknown")
53
+
52
54
  request_data = {
55
+ "agent_id": agent_id,
53
56
  "tool_name": tool_name,
54
57
  "kwargs": kwargs,
55
58
  }
@@ -1,4 +1,4 @@
1
- from .terminal_actions import terminal_action
1
+ from .terminal_actions import terminal_execute
2
2
 
3
3
 
4
- __all__ = ["terminal_action"]
4
+ __all__ = ["terminal_execute"]
@@ -1,53 +1,35 @@
1
- from typing import Any, Literal
1
+ from typing import Any
2
2
 
3
3
  from strix.tools.registry import register_tool
4
4
 
5
5
  from .terminal_manager import get_terminal_manager
6
6
 
7
7
 
8
- TerminalAction = Literal["new_terminal", "send_input", "wait", "close"]
9
-
10
-
11
8
  @register_tool
12
- def terminal_action(
13
- action: TerminalAction,
14
- inputs: list[str] | None = None,
15
- time: float | None = None,
9
+ def terminal_execute(
10
+ command: str,
11
+ is_input: bool = False,
12
+ timeout: float | None = None,
16
13
  terminal_id: str | None = None,
14
+ no_enter: bool = False,
17
15
  ) -> dict[str, Any]:
18
- def _validate_inputs(action_name: str, inputs: list[str] | None) -> None:
19
- if not inputs:
20
- raise ValueError(f"inputs parameter is required for {action_name} action")
21
-
22
- def _validate_time(time_param: float | None) -> None:
23
- if time_param is None:
24
- raise ValueError("time parameter is required for wait action")
25
-
26
- def _validate_action(action_name: str) -> None:
27
- raise ValueError(f"Unknown action: {action_name}")
28
-
29
16
  manager = get_terminal_manager()
30
17
 
31
18
  try:
32
- match action:
33
- case "new_terminal":
34
- return manager.create_terminal(terminal_id, inputs)
35
-
36
- case "send_input":
37
- _validate_inputs(action, inputs)
38
- assert inputs is not None
39
- return manager.send_input(terminal_id, inputs)
40
-
41
- case "wait":
42
- _validate_time(time)
43
- assert time is not None
44
- return manager.wait_terminal(terminal_id, time)
45
-
46
- case "close":
47
- return manager.close_terminal(terminal_id)
48
-
49
- case _:
50
- _validate_action(action) # type: ignore[unreachable]
51
-
19
+ return manager.execute_command(
20
+ command=command,
21
+ is_input=is_input,
22
+ timeout=timeout,
23
+ terminal_id=terminal_id,
24
+ no_enter=no_enter,
25
+ )
52
26
  except (ValueError, RuntimeError) as e:
53
- return {"error": str(e), "terminal_id": terminal_id, "snapshot": "", "is_running": False}
27
+ return {
28
+ "error": str(e),
29
+ "command": command,
30
+ "terminal_id": terminal_id or "default",
31
+ "content": "",
32
+ "status": "error",
33
+ "exit_code": None,
34
+ "working_dir": None,
35
+ }
@@ -1,117 +1,142 @@
1
1
  <tools>
2
- <tool name="terminal_action">
3
- <description>Perform terminal actions using a terminal emulator instance. Each terminal instance
4
- is PERSISTENT and remains active until explicitly closed, allowing for multi-step
5
- workflows and long-running processes.</description>
2
+ <tool name="terminal_execute">
3
+ <description>Execute a bash command in a persistent terminal session. The terminal maintains state (environment variables, current directory, running processes) between commands.</description>
6
4
  <parameters>
7
- <parameter name="action" type="string" required="true">
8
- <description>The terminal action to perform: - new_terminal: Create a new terminal instance. This MUST be the first action for each terminal tab. - send_input: Send keyboard input to the specified terminal. - wait: Pause execution for specified number of seconds. Can be also used to get the current terminal state (screenshot, output, etc.) after using other tools. - close: Close the specified terminal instance. This MUST be the final action for each terminal tab.</description>
5
+ <parameter name="command" type="string" required="true">
6
+ <description>The bash command to execute. Can be empty to check output of running commands (will wait for timeout period to collect output).
7
+
8
+ Supported special keys and sequences (based on official tmux key names):
9
+ - Control sequences: C-c, C-d, C-z, C-a, C-e, C-k, C-l, C-u, C-w, etc. (also ^c, ^d, etc.)
10
+ - Navigation keys: Up, Down, Left, Right, Home, End
11
+ - Page keys: PageUp, PageDown, PgUp, PgDn, PPage, NPage
12
+ - Function keys: F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12
13
+ - Special keys: Enter, Escape, Space, Tab, BTab, BSpace, DC, IC
14
+ - Note: Use official tmux names (BSpace not Backspace, DC not Delete, IC not Insert, Escape not Esc)
15
+ - Meta/Alt sequences: M-key (e.g., M-f, M-b) - tmux official modifier
16
+ - Shift sequences: S-key (e.g., S-F6, S-Tab, S-Left)
17
+ - Combined modifiers: C-S-key, C-M-key, S-M-key, etc.
18
+
19
+ Special keys work automatically - no need to set is_input=true for keys like C-c, C-d, etc.
20
+ These are useful for interacting with vim, emacs, REPLs, and other interactive applications.</description>
9
21
  </parameter>
10
- <parameter name="inputs" type="string" required="false">
11
- <description>Required for 'new_terminal' and 'send_input' actions: - List of inputs to send to terminal. Each element in the list MUST be one of the following: - Regular text: "hello", "world", etc. - Literal text (not interpreted as special keys): prefix with "literal:" e.g., "literal:Home", "literal:Escape", "literal:Enter" to send these as text - Enter - Space - Backspace - Escape: "Escape", "^[", "C-[" - Tab: "Tab" - Arrow keys: "Left", "Right", "Up", "Down" - Navigation: "Home", "End", "PageUp", "PageDown" - Function keys: "F1" through "F12" Modifier keys supported with prefixes: - ^ or C- : Control (e.g., "^c", "C-c") - S- : Shift (e.g., "S-F6") - A- : Alt (e.g., "A-Home") - Combined modifiers for arrows: "S-A-Up", "C-S-Left" - Inputs MUST in all cases be sent as a LIST of strings, even if you are only sending one input. - Sending Inputs as a single string will NOT work.</description>
22
+ <parameter name="is_input" type="boolean" required="false">
23
+ <description>If true, the command is sent as input to a currently running process. If false (default), the command is executed as a new bash command.
24
+ Note: Special keys (C-c, C-d, etc.) automatically work when a process is running - you don't need to set is_input=true for them.
25
+ Use is_input=true for regular text input to running processes.</description>
12
26
  </parameter>
13
- <parameter name="time" type="string" required="false">
14
- <description>Required for 'wait' action. Number of seconds to pause execution. Can be fractional (e.g., 0.5 for half a second).</description>
27
+ <parameter name="timeout" type="number" required="false">
28
+ <description>Optional timeout in seconds for command execution. If not provided, uses default timeout behavior. Set to higher values for long-running commands like installations or tests. Default is 10 seconds.</description>
15
29
  </parameter>
16
30
  <parameter name="terminal_id" type="string" required="false">
17
- <description>Identifier for the terminal instance. Required for all actions except the first 'new_terminal' action. Allows managing multiple concurrent terminal tabs. - For 'new_terminal': if not provided, a default terminal is created. If provided, creates a new terminal with that ID. - For other actions: specifies which terminal instance to operate on. - Default terminal ID is "default" if not specified.</description>
31
+ <description>Identifier for the terminal session. Defaults to "default". Use different IDs to manage multiple concurrent terminal sessions.</description>
32
+ </parameter>
33
+ <parameter name="no_enter" type="boolean" required="false">
34
+ <description>If true, don't automatically add Enter/newline after the command. Useful for:
35
+ - Interactive prompts where you want to send keys without submitting
36
+ - Navigation keys in full-screen applications
37
+
38
+ Examples:
39
+ - terminal_execute("gg", is_input=true, no_enter=true) # Vim: go to top
40
+ - terminal_execute("5j", is_input=true, no_enter=true) # Vim: move down 5 lines
41
+ - terminal_execute("i", is_input=true, no_enter=true) # Vim: insert mode</description>
18
42
  </parameter>
19
43
  </parameters>
20
44
  <returns type="Dict[str, Any]">
21
- <description>Response containing: - snapshot: raw representation of current terminal state where you can see the output of the command - terminal_id: the ID of the terminal instance that was operated on</description>
45
+ <description>Response containing:
46
+ - content: Command output
47
+ - exit_code: Exit code of the command (only for completed commands)
48
+ - command: The executed command
49
+ - terminal_id: The terminal session ID
50
+ - status: Command status ('completed' or 'running')
51
+ - working_dir: Current working directory after command execution</description>
22
52
  </returns>
23
53
  <notes>
24
54
  Important usage rules:
25
- 1. PERSISTENCE: Terminal instances remain active and maintain their state (environment
26
- variables, current directory, running processes) until explicitly closed with the
27
- 'close' action. This allows for multi-step workflows across multiple tool calls.
28
- 2. MULTIPLE TERMINALS: You can run multiple terminal instances concurrently by using
29
- different terminal_id values. Each terminal operates independently.
30
- 3. Terminal interaction MUST begin with 'new_terminal' action for each terminal instance.
31
- 4. Only one action can be performed per call.
32
- 5. Input handling:
33
- - Regular text is sent as-is
34
- - Literal text: prefix with "literal:" to send special key names as literal text
35
- - Special keys must match supported key names
36
- - Modifier combinations follow specific syntax
37
- - Control can be specified as ^ or C- prefix
38
- - Shift (S-) works with special keys only
39
- - Alt (A-) works with any character/key
40
- 6. Wait action:
41
- - Time is specified in seconds
42
- - Can be used to wait for command completion
43
- - Can be fractional (e.g., 0.5 seconds)
44
- - Snapshot and output are captured after the wait
45
- - You should estimate the time it will take to run the command and set the wait time accordingly.
46
- - It can be from a few seconds to a few minutes, choose wisely depending on the command you are running and the task.
47
- 7. The terminal can operate concurrently with other tools. You may invoke
48
- browser, proxy, or other tools (in separate assistant messages) while maintaining
49
- active terminal sessions.
50
- 8. You do not need to close terminals after you are done, but you can if you want to
51
- free up resources.
52
- 9. You MUST end the inputs list with an "Enter" if you want to run the command, as
53
- it is not sent automatically.
54
- 10. AUTOMATIC SPACING BEHAVIOR:
55
- - Consecutive regular text inputs have spaces automatically added between them
56
- - This is helpful for shell commands: ["ls", "-la"] becomes "ls -la"
57
- - This causes problems for compound commands: [":", "w", "q"] becomes ": w q"
58
- - Use "literal:" prefix to bypass spacing: [":", "literal:wq"] becomes ":wq"
59
- - Special keys (Enter, Space, etc.) and literal strings never trigger spacing
60
- 11. WHEN TO USE LITERAL PREFIX:
61
- - Vim commands: [":", "literal:wq", "Enter"] instead of [":", "w", "q", "Enter"]
62
- - Any sequence where exact character positioning matters
63
- - When you need multiple characters sent as a single unit
64
- 12. Do NOT use terminal actions for file editing or writing. Use the replace_in_file,
65
- write_to_file, or read_file tools instead.
66
- 13. PREFER SIMPLE COMMANDS: Avoid complex multiline commands with nested quotes or
67
- complex syntax. Break down complex operations into simpler, individual commands
68
- for better reliability and readability. Never send multiple commands in a single
69
- input list with multiple "Enter" keys - execute one command at a time instead.
55
+ 1. PERSISTENT SESSION: The terminal maintains state between commands. Environment variables,
56
+ current directory, and running processes persist across multiple tool calls.
57
+
58
+ 2. COMMAND EXECUTION: Execute one command at a time. For multiple commands, chain them with
59
+ && or ; operators, or make separate tool calls.
60
+
61
+ 3. LONG-RUNNING COMMANDS:
62
+ - Commands never get killed automatically - they keep running in background
63
+ - Set timeout to control how long to wait for output before returning
64
+ - Use empty command "" to check progress (waits for timeout period to collect output)
65
+ - Use C-c, C-d, C-z to interrupt processes (works automatically, no is_input needed)
66
+
67
+ 4. TIMEOUT HANDLING:
68
+ - Timeout controls how long to wait before returning current output
69
+ - Commands are NEVER killed on timeout - they keep running
70
+ - After timeout, you can run new commands or check progress with empty command
71
+ - All commands return status "completed" - you have full control
72
+
73
+ 5. MULTIPLE TERMINALS: Use different terminal_id values to run multiple concurrent sessions.
74
+
75
+ 6. INTERACTIVE PROCESSES:
76
+ - Special keys (C-c, C-d, etc.) work automatically when a process is running
77
+ - Use is_input=true for regular text input to running processes like:
78
+ * Interactive shells, REPLs, or prompts
79
+ * Long-running applications waiting for input
80
+ * Background processes that need interaction
81
+ - Use no_enter=true for stuff like Vim navigation, password typing, or multi-step commands
82
+
83
+ 7. WORKING DIRECTORY: The terminal tracks and returns the current working directory.
84
+ Use absolute paths or cd commands to change directories as needed.
85
+
86
+ 8. OUTPUT HANDLING: Large outputs are automatically truncated. The tool provides
87
+ the most relevant parts of the output for analysis.
70
88
  </notes>
71
89
  <examples>
72
- # Create new terminal with Node.js (default terminal)
73
- <function=terminal_action>
74
- <parameter=action>new_terminal</parameter>
75
- <parameter=inputs>["node", "Enter"]</parameter>
90
+ # Execute a simple command
91
+ <function=terminal_execute>
92
+ <parameter=command>ls -la</parameter>
93
+ </function>
94
+
95
+ # Run a command with custom timeout
96
+ <function=terminal_execute>
97
+ <parameter=command>npm install</parameter>
98
+ <parameter=timeout>120</parameter>
99
+ </function>
100
+
101
+ # Check progress of running command (waits for timeout to collect output)
102
+ <function=terminal_execute>
103
+ <parameter=command></parameter>
104
+ <parameter=timeout>5</parameter>
76
105
  </function>
77
106
 
78
- # Create a second (parallel) terminal instance for Python
79
- <function=terminal_action>
80
- <parameter=action>new_terminal</parameter>
81
- <parameter=terminal_id>python_terminal</parameter>
82
- <parameter=inputs>["python3", "Enter"]</parameter>
107
+ # Start a background service
108
+ <function=terminal_execute>
109
+ <parameter=command>python app.py > server.log 2>&1 &</parameter>
83
110
  </function>
84
111
 
85
- # Send command to the default terminal
86
- <function=terminal_action>
87
- <parameter=action>send_input</parameter>
88
- <parameter=inputs>["require('crypto').randomBytes(1000000).toString('hex')",
89
- "Enter"]</parameter>
112
+ # Interact with a running process
113
+ <function=terminal_execute>
114
+ <parameter=command>y</parameter>
115
+ <parameter=is_input>true</parameter>
90
116
  </function>
91
117
 
92
- # Wait for previous action on default terminal
93
- <function=terminal_action>
94
- <parameter=action>wait</parameter>
95
- <parameter=time>2.0</parameter>
118
+ # Interrupt a running process (special keys work automatically)
119
+ <function=terminal_execute>
120
+ <parameter=command>C-c</parameter>
96
121
  </function>
97
122
 
98
- # Send multiple inputs with special keys to current terminal
99
- <function=terminal_action>
100
- <parameter=action>send_input</parameter>
101
- <parameter=inputs>["sqlmap -u 'http://example.com/page.php?id=1' --batch", "Enter", "y",
102
- "Enter", "n", "Enter", "n", "Enter"]</parameter>
123
+ # Send Escape key (use official tmux name)
124
+ <function=terminal_execute>
125
+ <parameter=command>Escape</parameter>
126
+ <parameter=is_input>true</parameter>
103
127
  </function>
104
128
 
105
- # WRONG: Vim command with automatic spacing (becomes ": w q")
106
- <function=terminal_action>
107
- <parameter=action>send_input</parameter>
108
- <parameter=inputs>[":", "w", "q", "Enter"]</parameter>
129
+ # Use a different terminal session
130
+ <function=terminal_execute>
131
+ <parameter=command>python3</parameter>
132
+ <parameter=terminal_id>python_session</parameter>
109
133
  </function>
110
134
 
111
- # CORRECT: Vim command using literal prefix (becomes ":wq")
112
- <function=terminal_action>
113
- <parameter=action>send_input</parameter>
114
- <parameter=inputs>[":", "literal:wq", "Enter"]</parameter>
135
+ # Send input to Python REPL in specific session
136
+ <function=terminal_execute>
137
+ <parameter=command>print("Hello World")</parameter>
138
+ <parameter=is_input>true</parameter>
139
+ <parameter=terminal_id>python_session</parameter>
115
140
  </function>
116
141
  </examples>
117
142
  </tool>