This commit is contained in:
@@ -31,7 +31,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- GEMINI_API_KEY=${GEMINI_API_KEY}
|
- GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://redis:6379/0
|
||||||
- WORKSPACE_ROOT=${WORKSPACE_ROOT:-/workspace}
|
# Use HOME not PWD: Claude needs access to any absolute file path, not just current project,
|
||||||
|
# and Claude Code could be running from multiple locations at the same time
|
||||||
|
- WORKSPACE_ROOT=${WORKSPACE_ROOT:-${HOME}}
|
||||||
volumes:
|
volumes:
|
||||||
- ${HOME:-/tmp}:/workspace:ro
|
- ${HOME:-/tmp}:/workspace:ro
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
|
|||||||
@@ -37,8 +37,12 @@ GEMINI_API_KEY=$API_KEY_VALUE
|
|||||||
# Redis configuration (automatically set for Docker Compose)
|
# Redis configuration (automatically set for Docker Compose)
|
||||||
REDIS_URL=redis://redis:6379/0
|
REDIS_URL=redis://redis:6379/0
|
||||||
|
|
||||||
# Workspace root (automatically set for Docker Compose)
|
# Workspace root - host path that maps to /workspace in container
|
||||||
WORKSPACE_ROOT=/workspace
|
# This should be the host directory path that contains all files Claude might reference
|
||||||
|
# We use $HOME (not $PWD) because Claude needs access to ANY absolute file path,
|
||||||
|
# not just files within the current project directory. Additionally, Claude Code
|
||||||
|
# could be running from multiple locations at the same time.
|
||||||
|
WORKSPACE_ROOT=$HOME
|
||||||
EOF
|
EOF
|
||||||
echo "✅ Created .env file with Redis configuration"
|
echo "✅ Created .env file with Redis configuration"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -235,9 +235,9 @@ class TestCollaborationWorkflow:
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = json.loads(result[0].text)
|
response = json.loads(result[0].text)
|
||||||
assert (
|
assert response["status"] == "requires_clarification", (
|
||||||
response["status"] == "requires_clarification"
|
"Should request clarification when asked about dependencies without package files"
|
||||||
), "Should request clarification when asked about dependencies without package files"
|
)
|
||||||
|
|
||||||
clarification = json.loads(response["content"])
|
clarification = json.loads(response["content"])
|
||||||
assert "package.json" in str(clarification["files_needed"]), "Should specifically request package.json"
|
assert "package.json" in str(clarification["files_needed"]), "Should specifically request package.json"
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ class TestConversationFlow:
|
|||||||
# REQUEST 6: Try to exceed MAX_CONVERSATION_TURNS limit - should fail
|
# REQUEST 6: Try to exceed MAX_CONVERSATION_TURNS limit - should fail
|
||||||
turns_at_limit = [
|
turns_at_limit = [
|
||||||
ConversationTurn(
|
ConversationTurn(
|
||||||
role="assistant" if i % 2 == 0 else "user", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:30Z"
|
role="assistant" if i % 2 == 0 else "user", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:30Z"
|
||||||
)
|
)
|
||||||
for i in range(MAX_CONVERSATION_TURNS)
|
for i in range(MAX_CONVERSATION_TURNS)
|
||||||
]
|
]
|
||||||
@@ -423,7 +423,9 @@ class TestConversationFlow:
|
|||||||
# Mock context with current turns
|
# Mock context with current turns
|
||||||
turns = [
|
turns = [
|
||||||
ConversationTurn(
|
ConversationTurn(
|
||||||
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z"
|
role="user" if i % 2 == 0 else "assistant",
|
||||||
|
content=f"Turn {i + 1}",
|
||||||
|
timestamp="2023-01-01T00:00:00Z",
|
||||||
)
|
)
|
||||||
for i in range(turn_num)
|
for i in range(turn_num)
|
||||||
]
|
]
|
||||||
@@ -445,7 +447,7 @@ class TestConversationFlow:
|
|||||||
# Now we should be at the limit - create final context
|
# Now we should be at the limit - create final context
|
||||||
final_turns = [
|
final_turns = [
|
||||||
ConversationTurn(
|
ConversationTurn(
|
||||||
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z"
|
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:00Z"
|
||||||
)
|
)
|
||||||
for i in range(MAX_CONVERSATION_TURNS)
|
for i in range(MAX_CONVERSATION_TURNS)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ class TestThinkingModes:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for tool, expected_default in tools:
|
for tool, expected_default in tools:
|
||||||
assert (
|
assert tool.get_default_thinking_mode() == expected_default, (
|
||||||
tool.get_default_thinking_mode() == expected_default
|
f"{tool.__class__.__name__} should default to {expected_default}"
|
||||||
), f"{tool.__class__.__name__} should default to {expected_default}"
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("tools.base.BaseTool.create_model")
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ from .token_utils import MAX_CONTEXT_TOKENS, estimate_tokens
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Get workspace root for Docker path translation
|
# Get workspace root for Docker path translation
|
||||||
# When running in Docker with a mounted workspace, WORKSPACE_ROOT contains
|
# IMPORTANT: WORKSPACE_ROOT should contain the HOST path (e.g., /Users/john/project)
|
||||||
# the host path that corresponds to /workspace in the container
|
# that gets mounted to /workspace in the Docker container. This enables proper
|
||||||
|
# path translation between host absolute paths and container workspace paths.
|
||||||
WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT")
|
WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT")
|
||||||
CONTAINER_WORKSPACE = Path("/workspace")
|
CONTAINER_WORKSPACE = Path("/workspace")
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ DANGEROUS_WORKSPACE_PATHS = {
|
|||||||
"/var",
|
"/var",
|
||||||
"/root",
|
"/root",
|
||||||
"/home",
|
"/home",
|
||||||
|
"/workspace", # Container path - WORKSPACE_ROOT should be host path
|
||||||
"C:\\",
|
"C:\\",
|
||||||
"C:\\Windows",
|
"C:\\Windows",
|
||||||
"C:\\Program Files",
|
"C:\\Program Files",
|
||||||
@@ -54,7 +56,17 @@ if WORKSPACE_ROOT:
|
|||||||
# Resolve to canonical path for comparison
|
# Resolve to canonical path for comparison
|
||||||
resolved_workspace = Path(WORKSPACE_ROOT).resolve()
|
resolved_workspace = Path(WORKSPACE_ROOT).resolve()
|
||||||
|
|
||||||
# Check against dangerous paths
|
# Special check for /workspace - common configuration mistake
|
||||||
|
if str(resolved_workspace) == "/workspace":
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Configuration Error: WORKSPACE_ROOT should be set to the HOST path, not the container path. "
|
||||||
|
f"Found: WORKSPACE_ROOT={WORKSPACE_ROOT} "
|
||||||
|
f"Expected: WORKSPACE_ROOT should be set to your host directory path (e.g., $HOME) "
|
||||||
|
f"that contains all files Claude might reference. "
|
||||||
|
f"This path gets mounted to /workspace inside the Docker container."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check against other dangerous paths
|
||||||
if str(resolved_workspace) in DANGEROUS_WORKSPACE_PATHS:
|
if str(resolved_workspace) in DANGEROUS_WORKSPACE_PATHS:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Security Error: WORKSPACE_ROOT '{WORKSPACE_ROOT}' is set to a dangerous system directory. "
|
f"Security Error: WORKSPACE_ROOT '{WORKSPACE_ROOT}' is set to a dangerous system directory. "
|
||||||
@@ -181,12 +193,17 @@ def translate_path_for_environment(path_str: str) -> str:
|
|||||||
|
|
||||||
This is the unified path translation function that should be used by all
|
This is the unified path translation function that should be used by all
|
||||||
tools and utilities throughout the codebase. It handles:
|
tools and utilities throughout the codebase. It handles:
|
||||||
1. Docker host-to-container path translation
|
1. Docker host-to-container path translation (host paths -> /workspace/...)
|
||||||
2. Direct mode (no translation needed)
|
2. Direct mode (no translation needed)
|
||||||
3. Security validation and error handling
|
3. Security validation and error handling
|
||||||
|
|
||||||
|
Docker Path Translation Logic:
|
||||||
|
- Input: /Users/john/project/src/file.py (host path from Claude)
|
||||||
|
- WORKSPACE_ROOT: /Users/john/project (host path in env var)
|
||||||
|
- Output: /workspace/src/file.py (container path for file operations)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path_str: Original path string from the client
|
path_str: Original path string from the client (absolute host path)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Translated path appropriate for the current environment
|
Translated path appropriate for the current environment
|
||||||
|
|||||||
Reference in New Issue
Block a user