Skip to content

Commit 2cae74e

Browse files
committed
refactor(shell): remove PS1 metadata system and simplify command output handling
This commit removes the complex PS1 metadata system that was previously used to track command execution details like exit codes, working directories, and Python interpreter paths. The system proved to be unreliable across different shell environments and added unnecessary complexity. Key changes: - Removed CmdOutputMetadata class and all related PS1 prompt generation/parsing logic - Simplified CommandResult class to focus on core command output without metadata - Updated BashSession to handle command output more directly without PS1 parsing - Improved output extraction logic to be more robust against shell decorations - Added fast_test_mode flag to BashSessionExecutor for faster test execution - Updated all tests to work with the simplified output handling system - Removed unused imports and dependencies related to the old metadata system The new approach provides more reliable command execution tracking while being simpler to maintain and more compatible across different shell environments.
1 parent db23971 commit 2cae74e

11 files changed

+145
-617
lines changed

mcp_claude_code/prompts/project_system.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Here is useful information about the environment you are running in:
44
55
<env>
6-
Working directory: {working_directory}
6+
Working directory: {working_directory} (You need cd to this directory by yourself)
77
Is directory a git repo: {is_git_repo}
88
Platform: {platform}
99
OS Version: {os_version}

mcp_claude_code/tools/filesystem/grep.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ async def call(
386386
pattern = params.get("pattern")
387387
path: str = params.get("path", ".")
388388
# Support both 'include' and legacy 'file_pattern' parameter for backward compatibility
389-
include: str = params.get("include")
389+
include: str = params.get("include") or params.get("file_pattern")
390390

391391
# Validate required parameters for direct calls (not through MCP framework)
392392
if pattern is None:

mcp_claude_code/tools/shell/base.py

Lines changed: 4 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
including command execution, script running, and process management.
55
"""
66

7-
import json
8-
import re
97
from abc import ABC, abstractmethod
108
from enum import Enum
119
from typing import Any, Self, final
1210

1311
from fastmcp import Context as MCPContext
14-
from pydantic import BaseModel
1512

1613
from mcp_claude_code.tools.common.base import BaseTool
1714
from mcp_claude_code.tools.common.permissions import PermissionManager
@@ -26,80 +23,6 @@ class BashCommandStatus(Enum):
2623
HARD_TIMEOUT = "hard_timeout"
2724

2825

29-
# PS1 metadata constants
30-
CMD_OUTPUT_PS1_BEGIN = "\n###PS1JSON###\n"
31-
CMD_OUTPUT_PS1_END = "\n###PS1END###"
32-
CMD_OUTPUT_METADATA_PS1_REGEX = re.compile(
33-
f"^{CMD_OUTPUT_PS1_BEGIN.strip()}(.*?){CMD_OUTPUT_PS1_END.strip()}",
34-
re.DOTALL | re.MULTILINE,
35-
)
36-
37-
38-
class CmdOutputMetadata(BaseModel):
39-
"""Rich metadata captured from PS1 prompts and command execution."""
40-
41-
exit_code: int = -1
42-
pid: int = -1
43-
username: str | None = None
44-
hostname: str | None = None
45-
working_dir: str | None = None
46-
py_interpreter_path: str | None = None
47-
prefix: str = "" # Prefix to add to command output
48-
suffix: str = "" # Suffix to add to command output
49-
50-
@classmethod
51-
def to_ps1_prompt(cls) -> str:
52-
"""Convert the required metadata into a PS1 prompt."""
53-
prompt = CMD_OUTPUT_PS1_BEGIN
54-
json_str = json.dumps(
55-
{
56-
"pid": "$!",
57-
"exit_code": "$?",
58-
"username": r"\u",
59-
"hostname": r"\h",
60-
"working_dir": r"$(pwd)",
61-
"py_interpreter_path": r'$(which python 2>/dev/null || echo "")',
62-
},
63-
indent=2,
64-
)
65-
# Make sure we escape double quotes in the JSON string
66-
# So that PS1 will keep them as part of the output
67-
prompt += json_str.replace('"', r"\"")
68-
prompt += CMD_OUTPUT_PS1_END + "\n" # Ensure there's a newline at the end
69-
return prompt
70-
71-
@classmethod
72-
def matches_ps1_metadata(cls, string: str) -> list[re.Match[str]]:
73-
"""Find all PS1 metadata matches in a string."""
74-
matches = []
75-
for match in CMD_OUTPUT_METADATA_PS1_REGEX.finditer(string):
76-
try:
77-
json.loads(match.group(1).strip()) # Try to parse as JSON
78-
matches.append(match)
79-
except json.JSONDecodeError:
80-
continue # Skip if not valid JSON
81-
return matches
82-
83-
@classmethod
84-
def from_ps1_match(cls, match: re.Match[str]) -> Self:
85-
"""Extract the required metadata from a PS1 prompt."""
86-
metadata = json.loads(match.group(1))
87-
# Create a copy of metadata to avoid modifying the original
88-
processed = metadata.copy()
89-
# Convert numeric fields
90-
if "pid" in metadata:
91-
try:
92-
processed["pid"] = int(float(str(metadata["pid"])))
93-
except (ValueError, TypeError):
94-
processed["pid"] = -1
95-
if "exit_code" in metadata:
96-
try:
97-
processed["exit_code"] = int(float(str(metadata["exit_code"])))
98-
except (ValueError, TypeError):
99-
processed["exit_code"] = -1
100-
return cls(**processed)
101-
102-
10326
@final
10427
class CommandResult:
10528
"""Represents the result of a command execution with rich metadata."""
@@ -111,7 +34,6 @@ def __init__(
11134
stderr: str = "",
11235
error_message: str | None = None,
11336
session_id: str | None = None,
114-
metadata: CmdOutputMetadata | None = None,
11537
status: BashCommandStatus = BashCommandStatus.COMPLETED,
11638
command: str = "",
11739
):
@@ -123,7 +45,6 @@ def __init__(
12345
stderr: Standard error from the command
12446
error_message: Optional error message for failure cases
12547
session_id: Optional session ID used for the command execution
126-
metadata: Rich metadata from command execution
12748
status: Command execution status
12849
command: The original command that was executed
12950
"""
@@ -132,7 +53,6 @@ def __init__(
13253
self.stderr: str = stderr
13354
self.error_message: str | None = error_message
13455
self.session_id: str | None = session_id
135-
self.metadata: CmdOutputMetadata = metadata or CmdOutputMetadata()
13656
self.status: BashCommandStatus = status
13757
self.command: str = command
13858

@@ -180,7 +100,7 @@ def message(self) -> str:
180100
return f"Command `{self.command}` executed with exit code {self.return_code}."
181101

182102
def format_output(self, include_exit_code: bool = True) -> str:
183-
"""Format the command output as a string with rich metadata.
103+
"""Format the command output as a string.
184104
185105
Args:
186106
include_exit_code: Whether to include the exit code in the output
@@ -206,26 +126,9 @@ def format_output(self, include_exit_code: bool = True) -> str:
206126
if include_exit_code and (self.return_code != 0 or not self.error_message):
207127
result_parts.append(f"Exit code: {self.return_code}")
208128

209-
# Add working directory if available
210-
if self.metadata.working_dir:
211-
result_parts.append(f"Working directory: {self.metadata.working_dir}")
212-
213-
# Add Python interpreter if available
214-
if self.metadata.py_interpreter_path:
215-
result_parts.append(
216-
f"Python interpreter: {self.metadata.py_interpreter_path}"
217-
)
218-
219-
# Format the main output with prefix and suffix
220-
output_content = self.stdout
221-
if self.metadata.prefix:
222-
output_content = self.metadata.prefix + output_content
223-
if self.metadata.suffix:
224-
output_content = output_content + self.metadata.suffix
225-
226129
# Add stdout if present
227-
if output_content:
228-
result_parts.append(f"STDOUT:\n{output_content}")
130+
if self.stdout:
131+
result_parts.append(f"STDOUT:\n{self.stdout}")
229132

230133
# Add stderr if present
231134
if self.stderr:
@@ -235,26 +138,10 @@ def format_output(self, include_exit_code: bool = True) -> str:
235138
return "\n\n".join(result_parts)
236139

237140
def to_agent_observation(self) -> str:
238-
"""Format the result for agent consumption (similar to OpenHands)."""
141+
"""Format the result for agent consumption."""
239142
content = self.stdout
240-
if self.metadata.prefix:
241-
content = self.metadata.prefix + content
242-
if self.metadata.suffix:
243-
content = content + self.metadata.suffix
244143

245144
additional_info: list[str] = []
246-
if self.metadata.working_dir:
247-
additional_info.append(
248-
f"[Current working directory: {self.metadata.working_dir}]"
249-
)
250-
if self.metadata.py_interpreter_path:
251-
additional_info.append(
252-
f"[Python interpreter: {self.metadata.py_interpreter_path}]"
253-
)
254-
if self.metadata.exit_code != -1:
255-
additional_info.append(
256-
f"[Command finished with exit code {self.metadata.exit_code}]"
257-
)
258145
if self.session_id:
259146
additional_info.append(f"[Session ID: {self.session_id}]")
260147

0 commit comments

Comments
 (0)