openhands-tools 1.10.0__py3-none-any.whl → 1.11.1__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.
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
8
8
  if TYPE_CHECKING:
9
9
  from openhands.sdk.conversation.state import ConversationState
10
10
 
11
+ from openhands.sdk.logger import get_logger
11
12
  from openhands.sdk.tool import (
12
13
  ToolAnnotations,
13
14
  ToolDefinition,
@@ -20,7 +21,12 @@ from openhands.tools.file_editor.definition import (
20
21
  )
21
22
 
22
23
 
23
- # Hardcoded plan filename
24
+ logger = get_logger(__name__)
25
+
26
+ # Default config directory and plan filename
27
+ # PLAN.md is now stored in .agents_tmp/ to keep workspace root clean
28
+ # and separate agent temporary files from user content
29
+ DEFAULT_CONFIG_DIR = ".agents_tmp"
24
30
  PLAN_FILENAME = "PLAN.md"
25
31
 
26
32
 
@@ -62,11 +68,17 @@ class PlanningFileEditorTool(
62
68
  def create(
63
69
  cls,
64
70
  conv_state: "ConversationState",
71
+ plan_path: str | None = None,
65
72
  ) -> Sequence["PlanningFileEditorTool"]:
66
73
  """Initialize PlanningFileEditorTool.
67
74
 
68
75
  Args:
69
76
  conv_state: Conversation state to get working directory from.
77
+ plan_path: Optional absolute path to PLAN.md file. If not provided,
78
+ defaults to {working_dir}/.agents_tmp/PLAN.md.
79
+
80
+ Raises:
81
+ ValueError: If plan_path is provided but is not an absolute path.
70
82
  """
71
83
  # Import here to avoid circular imports
72
84
  from openhands.tools.planning_file_editor.impl import (
@@ -74,8 +86,31 @@ class PlanningFileEditorTool(
74
86
  )
75
87
 
76
88
  working_dir = conv_state.workspace.working_dir
77
- workspace_root = Path(working_dir).resolve()
78
- plan_path = str(workspace_root / PLAN_FILENAME)
89
+
90
+ # Validate plan_path is absolute if provided
91
+ if plan_path is not None and not Path(plan_path).is_absolute():
92
+ raise ValueError(f"plan_path must be an absolute path, got: {plan_path}")
93
+
94
+ # Use provided plan_path or fall back to .agents_tmp/PLAN.md at workspace root
95
+ if plan_path is None:
96
+ workspace_root = Path(working_dir).resolve()
97
+
98
+ # Check for legacy PLAN.md at workspace root
99
+ legacy_plan_path = workspace_root / PLAN_FILENAME
100
+ if legacy_plan_path.exists():
101
+ # Use legacy location for backward compatibility
102
+ new_recommended_path = (
103
+ workspace_root / DEFAULT_CONFIG_DIR / PLAN_FILENAME
104
+ )
105
+ logger.warning(
106
+ f"Found PLAN.md at legacy location {legacy_plan_path}. "
107
+ f"Consider moving it to {new_recommended_path} "
108
+ f"for consistency with OpenHands conventions."
109
+ )
110
+ plan_path = str(legacy_plan_path)
111
+ else:
112
+ # Use new default location
113
+ plan_path = str(workspace_root / DEFAULT_CONFIG_DIR / PLAN_FILENAME)
79
114
 
80
115
  # Initialize PLAN.md with headers if it doesn't exist
81
116
  plan_file = Path(plan_path)
@@ -83,7 +118,10 @@ class PlanningFileEditorTool(
83
118
  # Import here to avoid circular imports
84
119
  from openhands.tools.preset.planning import get_plan_headers
85
120
 
121
+ # Ensure parent directory exists
122
+ plan_file.parent.mkdir(parents=True, exist_ok=True)
86
123
  plan_file.write_text(get_plan_headers())
124
+ logger.info(f"Created new PLAN.md at {plan_path}")
87
125
 
88
126
  # Create executor with restricted edit access to PLAN.md only
89
127
  executor = PlanningFileEditorExecutor(
@@ -102,9 +102,13 @@ def register_planning_tools() -> None:
102
102
  logger.debug("Tool: PlanningFileEditorTool registered.")
103
103
 
104
104
 
105
- def get_planning_tools() -> list[Tool]:
105
+ def get_planning_tools(plan_path: str | None = None) -> list[Tool]:
106
106
  """Get the planning agent tool specifications.
107
107
 
108
+ Args:
109
+ plan_path: Optional absolute path to PLAN.md file. If provided, will be
110
+ passed to PlanningFileEditorTool via params.
111
+
108
112
  Returns:
109
113
  List of tools optimized for planning and analysis tasks, including
110
114
  file viewing and PLAN.md editing capabilities for advanced
@@ -117,10 +121,15 @@ def get_planning_tools() -> list[Tool]:
117
121
  from openhands.tools.grep import GrepTool
118
122
  from openhands.tools.planning_file_editor import PlanningFileEditorTool
119
123
 
124
+ # Build params for PlanningFileEditorTool if plan_path is provided
125
+ planning_tool_params = {}
126
+ if plan_path:
127
+ planning_tool_params["plan_path"] = plan_path
128
+
120
129
  return [
121
130
  Tool(name=GlobTool.name),
122
131
  Tool(name=GrepTool.name),
123
- Tool(name=PlanningFileEditorTool.name),
132
+ Tool(name=PlanningFileEditorTool.name, params=planning_tool_params),
124
133
  ]
125
134
 
126
135
 
@@ -5,8 +5,10 @@ import time
5
5
  from enum import Enum
6
6
 
7
7
  from openhands.sdk.logger import get_logger
8
+ from openhands.sdk.utils import maybe_truncate
8
9
  from openhands.tools.terminal.constants import (
9
10
  CMD_OUTPUT_PS1_END,
11
+ MAX_CMD_OUTPUT_SIZE,
10
12
  NO_CHANGE_TIMEOUT_SECONDS,
11
13
  POLL_INTERVAL,
12
14
  TIMEOUT_MESSAGE_TEMPLATE,
@@ -183,6 +185,10 @@ class TerminalSession(TerminalSessionBase):
183
185
  raw_command_output,
184
186
  metadata,
185
187
  )
188
+ command_output = maybe_truncate(
189
+ command_output, truncate_after=MAX_CMD_OUTPUT_SIZE
190
+ )
191
+
186
192
  self.prev_status = TerminalCommandStatus.COMPLETED
187
193
  self.prev_output = "" # Reset previous command output
188
194
  self._ready_for_next_command()
@@ -221,6 +227,9 @@ class TerminalSession(TerminalSessionBase):
221
227
  metadata,
222
228
  continue_prefix="[Below is the output of the previous command.]\n",
223
229
  )
230
+ command_output = maybe_truncate(
231
+ command_output, truncate_after=MAX_CMD_OUTPUT_SIZE
232
+ )
224
233
  return TerminalObservation.from_text(
225
234
  command=command,
226
235
  text=command_output,
@@ -257,6 +266,9 @@ class TerminalSession(TerminalSessionBase):
257
266
  metadata,
258
267
  continue_prefix="[Below is the output of the previous command.]\n",
259
268
  )
269
+ command_output = maybe_truncate(
270
+ command_output, truncate_after=MAX_CMD_OUTPUT_SIZE
271
+ )
260
272
  return TerminalObservation.from_text(
261
273
  command=command,
262
274
  exit_code=metadata.exit_code,
@@ -391,6 +403,9 @@ class TerminalSession(TerminalSessionBase):
391
403
  metadata,
392
404
  continue_prefix="[Below is the output of the previous command.]\n",
393
405
  )
406
+ command_output = maybe_truncate(
407
+ command_output, truncate_after=MAX_CMD_OUTPUT_SIZE
408
+ )
394
409
  obs = TerminalObservation.from_text(
395
410
  command=command,
396
411
  text=command_output,
@@ -141,7 +141,7 @@ def escape_bash_special_chars(command: str) -> str:
141
141
  remaining = command[last_pos:]
142
142
  parts.append(remaining)
143
143
  return "".join(parts)
144
- except (ParsingError, NotImplementedError, TypeError):
144
+ except (ParsingError, NotImplementedError, TypeError, AttributeError):
145
145
  logger.debug(
146
146
  f"Failed to parse bash commands for special characters escape\n[input]: "
147
147
  f"{command}\n[warning]: {traceback.format_exc()}\nThe original command "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-tools
3
- Version: 1.10.0
3
+ Version: 1.11.1
4
4
  Summary: OpenHands Tools - Runtime tools for AI agents
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -48,13 +48,13 @@ openhands/tools/grep/__init__.py,sha256=XJscA-rP35sCCpILkcCF36sd_RG8XdTx6X3N12W7
48
48
  openhands/tools/grep/definition.py,sha256=Nok-GtLv39a93y6s6BnJdL6nRERpPvk9LVX30lg6yHA,3970
49
49
  openhands/tools/grep/impl.py,sha256=mCib7eZi_nKCOBFMWv5-d1jSz3N7VpGOVnAs3KDf3PY,8647
50
50
  openhands/tools/planning_file_editor/__init__.py,sha256=edIuvR5ZnEXXsYfRt8xbrq15Z6vtdUq4AMguuuk1grI,197
51
- openhands/tools/planning_file_editor/definition.py,sha256=7wzE1qXoengRq80MjDI-XbWhbPVYLU1lThFO9Mq14wg,3927
51
+ openhands/tools/planning_file_editor/definition.py,sha256=1llmMozk0STSgIc-9GKc02MfEDb7YC0oLaRMCRG0Xko,5723
52
52
  openhands/tools/planning_file_editor/impl.py,sha256=W8Mdy7Qjbz90iKSmisw23-TcFtXwe7Vk373WvrScCS8,2218
53
53
  openhands/tools/preset/__init__.py,sha256=KougtVUTSj9D6AjR78EXmYRdITY6ZIaodCQuWfViXc0,913
54
54
  openhands/tools/preset/default.py,sha256=r6lAqHD-4ntjiOSfNvSxuAJtJqqTTik_mBIFZOE1w7Q,2686
55
55
  openhands/tools/preset/gemini.py,sha256=FG3BcJjnm6tDLIjg6EiDFTksx5lVVBInh_Jpza0mHpU,3136
56
56
  openhands/tools/preset/gpt5.py,sha256=44LOv6MncP9X9QU7pn1UfdjYl8ec8zKU9jUU7tUYR5I,2615
57
- openhands/tools/preset/planning.py,sha256=s05upBJ97JEZOfUJMkBW3xa5ZKgxZFklY1yHEBzqmhQ,5303
57
+ openhands/tools/preset/planning.py,sha256=SOe2A5Nr-jkgQAouCKGCyo3UfRKaUB8xbAFA59m69xA,5682
58
58
  openhands/tools/task_tracker/__init__.py,sha256=NVm3CE_CkIAUSjFexh7HLXMpibJ_Dh8J8OOow2SlE1I,302
59
59
  openhands/tools/task_tracker/definition.py,sha256=yGN_wks_-QpjRlfYiv5uOYyRTshaFuBwog0P62m5x0w,15772
60
60
  openhands/tools/terminal/__init__.py,sha256=L5iQMMVhQbB_p2DESxGKo67D9kvjU0XIVR8LBuQ7HiY,657
@@ -66,15 +66,15 @@ openhands/tools/terminal/terminal/__init__.py,sha256=UL08nepNd-k4jWBglQAKtg4aPjg
66
66
  openhands/tools/terminal/terminal/factory.py,sha256=Q9w-6AV-2aRpcTrKotdDDEKq1_7zc7I0MFb3sxXU374,4475
67
67
  openhands/tools/terminal/terminal/interface.py,sha256=g0nyhMzVsIGptfBnBdv6FESUtv7k-EnRI32KYYl2VO0,6854
68
68
  openhands/tools/terminal/terminal/subprocess_terminal.py,sha256=vKvcX_0H7ck3_BHWbir41NTI6srH4hCymOt48doq8js,16269
69
- openhands/tools/terminal/terminal/terminal_session.py,sha256=Y3n95JQfG_nid4g_j2873uVPjl-UQwiu3H3vwkijbrM,20159
69
+ openhands/tools/terminal/terminal/terminal_session.py,sha256=Mast2NgG5gW7Yefl61AFUf_HAMYaShZMQ_AQCUowuS4,20700
70
70
  openhands/tools/terminal/terminal/tmux_terminal.py,sha256=QMXeTs45WWeM6VzbHN3eCAYu9xXsHpFa6prsfNkOG3M,6187
71
- openhands/tools/terminal/utils/command.py,sha256=ZT6KMFQNIpMMWGKnG8HAlCJpaB34JcbYYfYBjcLNTQs,5434
71
+ openhands/tools/terminal/utils/command.py,sha256=RWmBsiXvEuDNQP4s52FvbWcTbm_vxdXYvI2aZHu0H_E,5450
72
72
  openhands/tools/tom_consult/__init__.py,sha256=av9VYJRWHmP4_5Eu2840PARtgOTRP84jZjqJ3zs8sRs,581
73
73
  openhands/tools/tom_consult/definition.py,sha256=MTdOCOJaEGgOXhlJ9-x0HdhD6JiKNT2DodEOdsB4RBg,8224
74
74
  openhands/tools/tom_consult/executor.py,sha256=LkWeltj-7ZgLhVZKZP4cuhxqg2YuyRnqSz5S0U9WElk,16656
75
75
  openhands/tools/utils/__init__.py,sha256=AgJX59QLQTulZqIvJnUqM7LxAgsFhtqmgol-lKLc7Wc,1253
76
76
  openhands/tools/utils/timeout.py,sha256=AjKNLZa8NqQ0HllZzqWZwZMj-8PEWHHBXwOlMjTygpA,399
77
- openhands_tools-1.10.0.dist-info/METADATA,sha256=7aBR3o2KlLBYrZxhtmkHxFhId6OPFw9fyUC6_5YJlKA,699
78
- openhands_tools-1.10.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
79
- openhands_tools-1.10.0.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
80
- openhands_tools-1.10.0.dist-info/RECORD,,
77
+ openhands_tools-1.11.1.dist-info/METADATA,sha256=QPPoOi1v8U7qcTc2JPjH9_mH4c8ExxCdxs_PfuE6_Dg,699
78
+ openhands_tools-1.11.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
79
+ openhands_tools-1.11.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
80
+ openhands_tools-1.11.1.dist-info/RECORD,,