wcgw 2.8.4__tar.gz → 2.8.6__tar.gz
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.
Potentially problematic release.
This version of wcgw might be problematic. Click here for more details.
- wcgw-2.8.6/Dockerfile +41 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/PKG-INFO +3 -2
- {wcgw-2.8.4 → wcgw-2.8.6}/README.md +2 -1
- {wcgw-2.8.4 → wcgw-2.8.6}/pyproject.toml +2 -2
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/__init__.py +1 -2
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/file_ops/diff_edit.py +7 -3
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/file_ops/search_replace.py +50 -9
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/server.py +15 -3
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/modes.py +2 -2
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/tools.py +97 -89
- wcgw-2.8.6/src/wcgw_cli/__init__.py +1 -0
- {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/anthropic_client.py +16 -12
- {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/cli.py +4 -4
- {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/openai_client.py +17 -12
- {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/openai_utils.py +4 -15
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/file_ops/test_search_replace.py +73 -61
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_anthropic_client_utils.py +34 -30
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_openai_utils.py +1 -1
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_extended.py +52 -33
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_shell.py +73 -73
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_command_validation.py +16 -14
- wcgw-2.8.6/tests/client/tools/test_docker_operations.py +39 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_error_handling.py +2 -0
- wcgw-2.8.6/tests/client/tools/test_error_handling_full.py +152 -0
- wcgw-2.8.6/tests/client/tools/test_execute_bash.py +110 -0
- wcgw-2.8.6/tests/client/tools/test_file_errors.py +22 -0
- wcgw-2.8.6/tests/client/tools/test_full_coverage.py +66 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_is_int.py +3 -3
- wcgw-2.8.6/tests/client/tools/test_large_blocks.py +37 -0
- wcgw-2.8.6/tests/client/tools/test_prompt_update.py +48 -0
- wcgw-2.8.6/tests/client/tools/test_terminal_output_full.py +114 -0
- wcgw-2.8.6/tests/client/tools/test_terminal_output_incremental.py +46 -0
- wcgw-2.8.6/tests/client/tools/test_terminal_output_raw.py +57 -0
- wcgw-2.8.6/tests/client/tools/test_terminal_sequence.py +50 -0
- wcgw-2.8.6/tests/client/tools/test_timeout_handling.py +100 -0
- wcgw-2.8.6/tests/client/tools/test_timeout_state.py +30 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_user_interaction.py +21 -15
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_anthropic_client.py +14 -18
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_basic.py +7 -9
- wcgw-2.8.6/tests/test_initialize.py +250 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/uv.lock +1 -1
- wcgw-2.8.4/tests/client/tools/test_docker_operations.py +0 -22
- wcgw-2.8.4/tests/client/tools/test_execute_bash.py +0 -71
- wcgw-2.8.4/tests/client/tools/test_large_blocks.py +0 -21
- {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-publish.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-tests.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-types.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.gitignore +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.gitmodules +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.python-version +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/.vscode/settings.json +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/LICENSE +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/gpt_action_json_schema.json +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/gpt_instructions.txt +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/openai.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.git +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.gitignore +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.python-version +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/LICENSE +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/README.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/RELEASE.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/SECURITY.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/README.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/pyproject.toml +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/uv.lock +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/common.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/computer_use.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/diff-instructions.txt +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/Readme.md +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/memory.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/display_tree.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/path_prob.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/repo_context.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/sys_utils.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/relay/serve.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/relay/static/privacy.txt +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/types_.py +0 -0
- {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/__main__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/static/claude-ss.jpg +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/static/computer-use.jpg +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/static/example.jpg +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/static/rocket-icon.png +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/static/ss1.png +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/file_ops/test_diff_edit.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_path_prob.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_repo_context.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_memory.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_basic.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_file_ops.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_files.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_validation.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/__init__.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_file_operations.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test1.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test2.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test_file.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_knowledge_transfer.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_render_terminal.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_terminal_output.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_write_file.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/conftest.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_common.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use_base.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use_shell.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_sys_utils.py +0 -0
- {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_tools.py +0 -0
wcgw-2.8.6/Dockerfile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
|
|
2
|
+
# Start from a Python base image with the necessary tools
|
|
3
|
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv
|
|
4
|
+
|
|
5
|
+
# Set the working directory in the container
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
# Copy the project's pyproject.toml and lock file for dependency installation
|
|
9
|
+
COPY pyproject.toml /app/
|
|
10
|
+
COPY uv.lock /app/
|
|
11
|
+
|
|
12
|
+
# Enable bytecode compilation and set link mode to copy for dependencies
|
|
13
|
+
ENV UV_COMPILE_BYTECODE=1
|
|
14
|
+
ENV UV_LINK_MODE=copy
|
|
15
|
+
|
|
16
|
+
# Install dependencies
|
|
17
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
18
|
+
uv sync --frozen --no-install-project --no-dev --no-editable
|
|
19
|
+
|
|
20
|
+
# Copy the entire project into the container
|
|
21
|
+
COPY . /app
|
|
22
|
+
|
|
23
|
+
# Install the project
|
|
24
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
25
|
+
uv sync --frozen --no-dev --no-editable
|
|
26
|
+
|
|
27
|
+
# Use a smaller image to run the application
|
|
28
|
+
FROM python:3.12-slim-bookworm
|
|
29
|
+
|
|
30
|
+
# Set the working directory in the container
|
|
31
|
+
WORKDIR /app
|
|
32
|
+
|
|
33
|
+
# Copy the installed application from the previous stage
|
|
34
|
+
COPY --from=uv /root/.local /root/.local
|
|
35
|
+
COPY --from=uv --chown=app:app /app/.venv /app/.venv
|
|
36
|
+
|
|
37
|
+
# Add the virtual environment to the PATH
|
|
38
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
39
|
+
|
|
40
|
+
# Specify the command to run on container start
|
|
41
|
+
ENTRYPOINT ["wcgw_mcp"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wcgw
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.6
|
|
4
4
|
Summary: Shell and coding agent on claude and chatgpt
|
|
5
5
|
Project-URL: Homepage, https://github.com/rusiaaman/wcgw
|
|
6
6
|
Author-email: Aman Rusia <gapypi@arcfu.com>
|
|
@@ -28,6 +28,7 @@ Requires-Dist: websockets>=13.1
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# Shell and Coding agent for Claude and Chatgpt
|
|
31
|
+
Empowering chat applications to code, build and run on your local machine.
|
|
31
32
|
|
|
32
33
|
- Claude - An MCP server on claude desktop for autonomous shell and coding agent. (mac only)
|
|
33
34
|
- Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
|
|
@@ -39,6 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
39
40
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
40
41
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
|
|
41
42
|
[](https://codecov.io/gh/rusiaaman/wcgw)
|
|
43
|
+
[](https://smithery.ai/server/wcgw)
|
|
42
44
|
|
|
43
45
|
## Updates
|
|
44
46
|
- [15 Jan 2025] Modes introduced: architect, code-writer, and all powerful wcgw mode.
|
|
@@ -128,7 +130,6 @@ _If there's an error in setting up_
|
|
|
128
130
|
- Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
|
|
129
131
|
|
|
130
132
|
### Alternative configuration using smithery (npx required)
|
|
131
|
-
[](https://smithery.ai/server/wcgw)
|
|
132
133
|
|
|
133
134
|
You need to first install uv using homebrew. `brew install uv`
|
|
134
135
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Shell and Coding agent for Claude and Chatgpt
|
|
2
|
+
Empowering chat applications to code, build and run on your local machine.
|
|
2
3
|
|
|
3
4
|
- Claude - An MCP server on claude desktop for autonomous shell and coding agent. (mac only)
|
|
4
5
|
- Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
|
|
@@ -10,6 +11,7 @@
|
|
|
10
11
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
11
12
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
|
|
12
13
|
[](https://codecov.io/gh/rusiaaman/wcgw)
|
|
14
|
+
[](https://smithery.ai/server/wcgw)
|
|
13
15
|
|
|
14
16
|
## Updates
|
|
15
17
|
- [15 Jan 2025] Modes introduced: architect, code-writer, and all powerful wcgw mode.
|
|
@@ -99,7 +101,6 @@ _If there's an error in setting up_
|
|
|
99
101
|
- Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
|
|
100
102
|
|
|
101
103
|
### Alternative configuration using smithery (npx required)
|
|
102
|
-
[](https://smithery.ai/server/wcgw)
|
|
103
104
|
|
|
104
105
|
You need to first install uv using homebrew. `brew install uv`
|
|
105
106
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
|
|
3
3
|
name = "wcgw"
|
|
4
|
-
version = "2.8.
|
|
4
|
+
version = "2.8.6"
|
|
5
5
|
description = "Shell and coding agent on claude and chatgpt"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.11, <3.13"
|
|
@@ -42,7 +42,7 @@ packages = ["src/wcgw", "src/mcp_wcgw_fork/src/mcp_wcgw"]
|
|
|
42
42
|
"src/wcgw" = "wcgw"
|
|
43
43
|
|
|
44
44
|
[project.scripts]
|
|
45
|
-
wcgw_local = "
|
|
45
|
+
wcgw_local = "wcgw_cli:app"
|
|
46
46
|
wcgw = "wcgw:listen"
|
|
47
47
|
wcgw_relay = "wcgw.relay.serve:run"
|
|
48
48
|
wcgw_mcp = "wcgw:mcp_server"
|
|
@@ -6,6 +6,10 @@ from typing import Callable, DefaultDict, Literal, Optional
|
|
|
6
6
|
TOLERANCE_TYPES = Literal["SILENT", "WARNING", "ERROR"]
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class SearchReplaceMatchError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
9
13
|
@dataclass
|
|
10
14
|
class Tolerance:
|
|
11
15
|
line_process: Callable[[str], str]
|
|
@@ -45,7 +49,7 @@ class FileEditOutput:
|
|
|
45
49
|
Got error while processing the following search block:
|
|
46
50
|
---
|
|
47
51
|
```
|
|
48
|
-
{
|
|
52
|
+
{"\n".join(search_)}
|
|
49
53
|
```
|
|
50
54
|
---
|
|
51
55
|
Error:
|
|
@@ -53,7 +57,7 @@ Error:
|
|
|
53
57
|
---
|
|
54
58
|
""")
|
|
55
59
|
if len(errors) >= max_errors:
|
|
56
|
-
raise
|
|
60
|
+
raise SearchReplaceMatchError("\n".join(errors))
|
|
57
61
|
if last_idx < span.start:
|
|
58
62
|
new_lines.extend(self.original_content[last_idx : span.start])
|
|
59
63
|
|
|
@@ -64,7 +68,7 @@ Error:
|
|
|
64
68
|
new_lines.extend(self.original_content[last_idx:])
|
|
65
69
|
|
|
66
70
|
if errors:
|
|
67
|
-
raise
|
|
71
|
+
raise SearchReplaceMatchError("\n".join(errors))
|
|
68
72
|
|
|
69
73
|
return new_lines, set(warnings)
|
|
70
74
|
|
|
@@ -1,32 +1,71 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Callable
|
|
3
3
|
|
|
4
|
-
from .diff_edit import FileEditInput, FileEditOutput
|
|
4
|
+
from .diff_edit import FileEditInput, FileEditOutput, SearchReplaceMatchError
|
|
5
5
|
|
|
6
|
+
# Global regex patterns
|
|
7
|
+
SEARCH_MARKER = re.compile(r"^<<<<<<+\s*SEARCH\s*$")
|
|
8
|
+
DIVIDER_MARKER = re.compile(r"^======*\s*$")
|
|
9
|
+
REPLACE_MARKER = re.compile(r"^>>>>>>+\s*REPLACE\s*$")
|
|
10
|
+
|
|
11
|
+
class SearchReplaceSyntaxError(Exception):
|
|
12
|
+
def __init__(self, message: str):
|
|
13
|
+
message =f"""Got syntax error while parsing search replace blocks:
|
|
14
|
+
{message}
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Make sure blocks are in correct sequence, and the markers are in separate lines:
|
|
18
|
+
|
|
19
|
+
<{'<<<<<< SEARCH'}
|
|
20
|
+
example old
|
|
21
|
+
=======
|
|
22
|
+
example new
|
|
23
|
+
>{'>>>>>> REPLACE'}
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(message)
|
|
6
27
|
|
|
7
28
|
def search_replace_edit(
|
|
8
29
|
lines: list[str], original_content: str, logger: Callable[[str], object]
|
|
9
30
|
) -> tuple[str, str]:
|
|
10
31
|
if not lines:
|
|
11
|
-
raise
|
|
32
|
+
raise SearchReplaceSyntaxError("Error: No input to search replace edit")
|
|
33
|
+
|
|
12
34
|
original_lines = original_content.split("\n")
|
|
13
35
|
n_lines = len(lines)
|
|
14
36
|
i = 0
|
|
15
37
|
search_replace_blocks = list[tuple[list[str], list[str]]]()
|
|
38
|
+
|
|
16
39
|
while i < n_lines:
|
|
17
|
-
if
|
|
40
|
+
if SEARCH_MARKER.match(lines[i]):
|
|
41
|
+
line_num = i + 1
|
|
18
42
|
search_block = []
|
|
19
43
|
i += 1
|
|
20
|
-
|
|
44
|
+
|
|
45
|
+
while i < n_lines and not DIVIDER_MARKER.match(lines[i]):
|
|
46
|
+
if SEARCH_MARKER.match(lines[i]) or REPLACE_MARKER.match(lines[i]):
|
|
47
|
+
raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker in SEARCH block: {lines[i]}")
|
|
21
48
|
search_block.append(lines[i])
|
|
22
49
|
i += 1
|
|
23
|
-
|
|
50
|
+
|
|
51
|
+
if i >= n_lines:
|
|
52
|
+
raise SearchReplaceSyntaxError(f"Line {line_num}: Unclosed SEARCH block - missing ======= marker")
|
|
53
|
+
|
|
24
54
|
if not search_block:
|
|
25
|
-
raise
|
|
55
|
+
raise SearchReplaceSyntaxError(f"Line {line_num}: SEARCH block cannot be empty")
|
|
56
|
+
|
|
57
|
+
i += 1
|
|
26
58
|
replace_block = []
|
|
27
|
-
|
|
59
|
+
|
|
60
|
+
while i < n_lines and not REPLACE_MARKER.match(lines[i]):
|
|
61
|
+
if SEARCH_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]):
|
|
62
|
+
raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker in REPLACE block: {lines[i]}")
|
|
28
63
|
replace_block.append(lines[i])
|
|
29
64
|
i += 1
|
|
65
|
+
|
|
66
|
+
if i >= n_lines:
|
|
67
|
+
raise SearchReplaceSyntaxError(f"Line {line_num}: Unclosed block - missing REPLACE marker")
|
|
68
|
+
|
|
30
69
|
i += 1
|
|
31
70
|
|
|
32
71
|
for line in search_block:
|
|
@@ -38,10 +77,12 @@ def search_replace_edit(
|
|
|
38
77
|
|
|
39
78
|
search_replace_blocks.append((search_block, replace_block))
|
|
40
79
|
else:
|
|
80
|
+
if REPLACE_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]):
|
|
81
|
+
raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker outside block: {lines[i]}")
|
|
41
82
|
i += 1
|
|
42
83
|
|
|
43
84
|
if not search_replace_blocks:
|
|
44
|
-
raise
|
|
85
|
+
raise SearchReplaceSyntaxError(
|
|
45
86
|
"No valid search replace blocks found, ensure your SEARCH/REPLACE blocks are formatted correctly"
|
|
46
87
|
)
|
|
47
88
|
|
|
@@ -80,7 +121,7 @@ def greedy_context_replace(
|
|
|
80
121
|
if len(best_matches) > 1:
|
|
81
122
|
# Duplicate found, try to ground using previous blocks.
|
|
82
123
|
if current_block_offset == 0:
|
|
83
|
-
raise
|
|
124
|
+
raise SearchReplaceMatchError(f"""
|
|
84
125
|
The following block matched more than once:
|
|
85
126
|
---
|
|
86
127
|
```
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
|
-
from pydantic import AnyUrl, ValidationError
|
|
7
|
-
|
|
8
7
|
import mcp_wcgw.server.stdio
|
|
9
8
|
import mcp_wcgw.types as types
|
|
10
9
|
from mcp_wcgw.server import NotificationOptions, Server
|
|
11
10
|
from mcp_wcgw.server.models import InitializationOptions
|
|
12
11
|
from mcp_wcgw.types import Tool as ToolParam
|
|
12
|
+
from pydantic import AnyUrl, ValidationError
|
|
13
13
|
|
|
14
14
|
from ...types_ import (
|
|
15
15
|
BashCommand,
|
|
@@ -35,6 +35,18 @@ COMPUTER_USE_ON_DOCKER_ENABLED = False
|
|
|
35
35
|
|
|
36
36
|
server = Server("wcgw")
|
|
37
37
|
|
|
38
|
+
# Log only time stamp
|
|
39
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s: %(message)s")
|
|
40
|
+
logger = logging.getLogger("wcgw")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Console:
|
|
44
|
+
def print(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
45
|
+
logger.info(msg)
|
|
46
|
+
|
|
47
|
+
def log(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
48
|
+
logger.info(msg)
|
|
49
|
+
|
|
38
50
|
|
|
39
51
|
@server.list_resources() # type: ignore
|
|
40
52
|
async def handle_list_resources() -> list[types.Resource]:
|
|
@@ -301,7 +313,7 @@ async def main(computer_use: bool) -> None:
|
|
|
301
313
|
tools.TIMEOUT = SLEEP_TIME_MAX_S
|
|
302
314
|
tools.TIMEOUT_WHILE_OUTPUT = 55
|
|
303
315
|
tools.OUTPUT_WAIT_PATIENCE = 5
|
|
304
|
-
tools.console =
|
|
316
|
+
tools.console = Console()
|
|
305
317
|
|
|
306
318
|
if computer_use:
|
|
307
319
|
COMPUTER_USE_ON_DOCKER_ENABLED = True
|
|
@@ -51,7 +51,7 @@ def code_writer_prompt(
|
|
|
51
51
|
allowed_commands: Literal["all"] | list[str],
|
|
52
52
|
) -> str:
|
|
53
53
|
base = """
|
|
54
|
-
You
|
|
54
|
+
You are now running in "code_writer" mode.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
57
|
path_prompt = """
|
|
@@ -134,7 +134,7 @@ Additional instructions:
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
"""
|
|
137
|
-
ARCHITECT_PROMPT = """You
|
|
137
|
+
ARCHITECT_PROMPT = """You are now running in "architect" mode. This means
|
|
138
138
|
- You are not allowed to edit or update any file. You are not allowed to create any file.
|
|
139
139
|
- You are not allowed to run any commands that may change disk, system configuration, packages or environment. Only read-only commands are allowed.
|
|
140
140
|
- Only run commands that allows you to explore the repository, understand the system or read anything of relevance.
|
|
@@ -20,6 +20,7 @@ from typing import (
|
|
|
20
20
|
Literal,
|
|
21
21
|
Optional,
|
|
22
22
|
ParamSpec,
|
|
23
|
+
Protocol,
|
|
23
24
|
Type,
|
|
24
25
|
TypeVar,
|
|
25
26
|
)
|
|
@@ -73,17 +74,13 @@ from .repo_ops.repo_context import get_repo_context
|
|
|
73
74
|
from .sys_utils import command_run
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
class
|
|
77
|
-
def print(self, *args, **kwargs
|
|
78
|
-
pass
|
|
77
|
+
class Console(Protocol):
|
|
78
|
+
def print(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
79
79
|
|
|
80
|
-
def log(self, *args, **kwargs
|
|
81
|
-
pass
|
|
80
|
+
def log(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
82
81
|
|
|
83
82
|
|
|
84
|
-
console:
|
|
85
|
-
style="magenta", highlight=False, markup=False
|
|
86
|
-
)
|
|
83
|
+
console: Console = rich.console.Console(style="magenta", highlight=False, markup=False)
|
|
87
84
|
|
|
88
85
|
TIMEOUT = 5
|
|
89
86
|
TIMEOUT_WHILE_OUTPUT = 20
|
|
@@ -133,8 +130,7 @@ def ask_confirmation(prompt: Confirmation) -> str:
|
|
|
133
130
|
return "Yes" if response.lower() == "y" else "No"
|
|
134
131
|
|
|
135
132
|
|
|
136
|
-
PROMPT_CONST = "
|
|
137
|
-
PROMPT = PROMPT_CONST
|
|
133
|
+
PROMPT_CONST = "#" + "@wcgw@#"
|
|
138
134
|
|
|
139
135
|
|
|
140
136
|
def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: # type: ignore
|
|
@@ -145,36 +141,36 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
|
|
|
145
141
|
try:
|
|
146
142
|
shell = pexpect.spawn(
|
|
147
143
|
cmd,
|
|
148
|
-
env={**os.environ, **{"PS1":
|
|
144
|
+
env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
|
|
149
145
|
echo=False,
|
|
150
146
|
encoding="utf-8",
|
|
151
147
|
timeout=TIMEOUT,
|
|
152
148
|
cwd=initial_dir,
|
|
153
149
|
)
|
|
154
150
|
shell.sendline(
|
|
155
|
-
f"export PROMPT_COMMAND= PS1={
|
|
151
|
+
f"export PROMPT_COMMAND= PS1={PROMPT_CONST}"
|
|
156
152
|
) # Unset prompt command to avoid interfering
|
|
157
|
-
shell.expect(
|
|
153
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
158
154
|
except Exception as e:
|
|
159
155
|
console.print(traceback.format_exc())
|
|
160
156
|
console.log(f"Error starting shell: {e}. Retrying without rc ...")
|
|
161
157
|
|
|
162
158
|
shell = pexpect.spawn(
|
|
163
159
|
"/bin/bash --noprofile --norc",
|
|
164
|
-
env={**os.environ, **{"PS1":
|
|
160
|
+
env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
|
|
165
161
|
echo=False,
|
|
166
162
|
encoding="utf-8",
|
|
167
163
|
timeout=TIMEOUT,
|
|
168
164
|
)
|
|
169
|
-
shell.sendline(f"export PS1={
|
|
170
|
-
shell.expect(
|
|
165
|
+
shell.sendline(f"export PS1={PROMPT_CONST}")
|
|
166
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
171
167
|
|
|
172
168
|
shell.sendline("stty -icanon -echo")
|
|
173
|
-
shell.expect(
|
|
169
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
174
170
|
shell.sendline("set +o pipefail")
|
|
175
|
-
shell.expect(
|
|
171
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
176
172
|
shell.sendline("export GIT_PAGER=cat PAGER=cat")
|
|
177
|
-
shell.expect(
|
|
173
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
178
174
|
return shell
|
|
179
175
|
|
|
180
176
|
|
|
@@ -186,42 +182,6 @@ def _is_int(mystr: str) -> bool:
|
|
|
186
182
|
return False
|
|
187
183
|
|
|
188
184
|
|
|
189
|
-
def _ensure_env_and_bg_jobs(shell: pexpect.spawn) -> Optional[int]: # type: ignore
|
|
190
|
-
if PROMPT != PROMPT_CONST:
|
|
191
|
-
return None
|
|
192
|
-
# First reset the prompt in case venv was sourced or other reasons.
|
|
193
|
-
shell.sendline(f"export PS1={PROMPT}")
|
|
194
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
195
|
-
# Reset echo also if it was enabled
|
|
196
|
-
shell.sendline("stty -icanon -echo")
|
|
197
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
198
|
-
shell.sendline("set +o pipefail")
|
|
199
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
200
|
-
shell.sendline("export GIT_PAGER=cat PAGER=cat")
|
|
201
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
202
|
-
shell.sendline("jobs | wc -l")
|
|
203
|
-
before = ""
|
|
204
|
-
|
|
205
|
-
while not _is_int(before): # Consume all previous output
|
|
206
|
-
try:
|
|
207
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
208
|
-
except pexpect.TIMEOUT:
|
|
209
|
-
console.print(f"Couldn't get exit code, before: {before}")
|
|
210
|
-
raise
|
|
211
|
-
|
|
212
|
-
before_val = shell.before
|
|
213
|
-
if not isinstance(before_val, str):
|
|
214
|
-
before_val = str(before_val)
|
|
215
|
-
assert isinstance(before_val, str)
|
|
216
|
-
before_lines = render_terminal_output(before_val)
|
|
217
|
-
before = "\n".join(before_lines).strip()
|
|
218
|
-
|
|
219
|
-
try:
|
|
220
|
-
return int(before)
|
|
221
|
-
except ValueError:
|
|
222
|
-
raise ValueError(f"Malformed output: {before}")
|
|
223
|
-
|
|
224
|
-
|
|
225
185
|
BASH_CLF_OUTPUT = Literal["repl", "pending"]
|
|
226
186
|
|
|
227
187
|
|
|
@@ -245,7 +205,7 @@ class BashState:
|
|
|
245
205
|
)
|
|
246
206
|
self._mode = mode or Modes.wcgw
|
|
247
207
|
self._whitelist_for_overwrite: set[str] = whitelist_for_overwrite or set()
|
|
248
|
-
|
|
208
|
+
self._prompt = PROMPT_CONST
|
|
249
209
|
self._init_shell()
|
|
250
210
|
|
|
251
211
|
@property
|
|
@@ -264,7 +224,47 @@ class BashState:
|
|
|
264
224
|
def write_if_empty_mode(self) -> WriteIfEmptyMode:
|
|
265
225
|
return self._write_if_empty_mode
|
|
266
226
|
|
|
227
|
+
def ensure_env_and_bg_jobs(self) -> Optional[int]:
|
|
228
|
+
if self._prompt != PROMPT_CONST:
|
|
229
|
+
return None
|
|
230
|
+
shell = self.shell
|
|
231
|
+
# First reset the prompt in case venv was sourced or other reasons.
|
|
232
|
+
shell.sendline(f"export PS1={self._prompt}")
|
|
233
|
+
shell.expect(self._prompt, timeout=0.2)
|
|
234
|
+
# Reset echo also if it was enabled
|
|
235
|
+
shell.sendline("stty -icanon -echo")
|
|
236
|
+
shell.expect(self._prompt, timeout=0.2)
|
|
237
|
+
shell.sendline("set +o pipefail")
|
|
238
|
+
shell.expect(self._prompt, timeout=0.2)
|
|
239
|
+
shell.sendline("export GIT_PAGER=cat PAGER=cat")
|
|
240
|
+
shell.expect(self._prompt, timeout=0.2)
|
|
241
|
+
shell.sendline("jobs | wc -l")
|
|
242
|
+
before = ""
|
|
243
|
+
counts = 0
|
|
244
|
+
while not _is_int(before): # Consume all previous output
|
|
245
|
+
try:
|
|
246
|
+
shell.expect(self._prompt, timeout=0.2)
|
|
247
|
+
except pexpect.TIMEOUT:
|
|
248
|
+
console.print(f"Couldn't get exit code, before: {before}")
|
|
249
|
+
raise
|
|
250
|
+
|
|
251
|
+
before_val = shell.before
|
|
252
|
+
if not isinstance(before_val, str):
|
|
253
|
+
before_val = str(before_val)
|
|
254
|
+
assert isinstance(before_val, str)
|
|
255
|
+
before_lines = render_terminal_output(before_val)
|
|
256
|
+
before = "\n".join(before_lines).strip()
|
|
257
|
+
counts += 1
|
|
258
|
+
if counts > 100:
|
|
259
|
+
raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
return int(before)
|
|
263
|
+
except ValueError:
|
|
264
|
+
raise ValueError(f"Malformed output: {before}")
|
|
265
|
+
|
|
267
266
|
def _init_shell(self) -> None:
|
|
267
|
+
self._prompt = PROMPT_CONST
|
|
268
268
|
self._state: Literal["repl"] | datetime.datetime = "repl"
|
|
269
269
|
self._is_in_docker: Optional[str] = ""
|
|
270
270
|
# Ensure self._cwd exists
|
|
@@ -273,11 +273,11 @@ class BashState:
|
|
|
273
273
|
self._bash_command_mode.bash_mode == "restricted_mode",
|
|
274
274
|
self._cwd,
|
|
275
275
|
)
|
|
276
|
-
|
|
276
|
+
|
|
277
277
|
self._pending_output = ""
|
|
278
278
|
|
|
279
279
|
# Get exit info to ensure shell is ready
|
|
280
|
-
|
|
280
|
+
self.ensure_env_and_bg_jobs()
|
|
281
281
|
|
|
282
282
|
@property
|
|
283
283
|
def shell(self) -> pexpect.spawn: # type: ignore
|
|
@@ -309,9 +309,13 @@ class BashState:
|
|
|
309
309
|
def cwd(self) -> str:
|
|
310
310
|
return self._cwd
|
|
311
311
|
|
|
312
|
+
@property
|
|
313
|
+
def prompt(self) -> str:
|
|
314
|
+
return self._prompt
|
|
315
|
+
|
|
312
316
|
def update_cwd(self) -> str:
|
|
313
317
|
self.shell.sendline("pwd")
|
|
314
|
-
self.shell.expect(
|
|
318
|
+
self.shell.expect(self._prompt, timeout=0.2)
|
|
315
319
|
before_val = self.shell.before
|
|
316
320
|
if not isinstance(before_val, str):
|
|
317
321
|
before_val = str(before_val)
|
|
@@ -391,6 +395,30 @@ class BashState:
|
|
|
391
395
|
def pending_output(self) -> str:
|
|
392
396
|
return self._pending_output
|
|
393
397
|
|
|
398
|
+
def update_repl_prompt(self, command: str) -> bool:
|
|
399
|
+
if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
|
|
400
|
+
self.shell.sendintr()
|
|
401
|
+
index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
|
|
402
|
+
if index == 0:
|
|
403
|
+
return True
|
|
404
|
+
before = self.shell.before or ""
|
|
405
|
+
assert before, "Something went wrong updating repl prompt"
|
|
406
|
+
self._prompt = before.split("\n")[-1].strip()
|
|
407
|
+
# Escape all regex
|
|
408
|
+
self._prompt = re.escape(self._prompt)
|
|
409
|
+
console.print(f"Trying to update prompt to: {self._prompt.encode()!r}")
|
|
410
|
+
index = 0
|
|
411
|
+
counts = 0
|
|
412
|
+
while index == 0:
|
|
413
|
+
# Consume all REPL prompts till now
|
|
414
|
+
index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
|
|
415
|
+
counts += 1
|
|
416
|
+
if counts > 100:
|
|
417
|
+
raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
|
|
418
|
+
console.print(f"Prompt updated to: {self._prompt}")
|
|
419
|
+
return True
|
|
420
|
+
return False
|
|
421
|
+
|
|
394
422
|
|
|
395
423
|
BASH_STATE = BashState(os.getcwd(), None, None, None, None)
|
|
396
424
|
INITIALIZED = False
|
|
@@ -547,28 +575,6 @@ WAITING_INPUT_MESSAGE = """A command is already running. NOTE: You can't run mul
|
|
|
547
575
|
"""
|
|
548
576
|
|
|
549
577
|
|
|
550
|
-
def update_repl_prompt(command: str) -> bool:
|
|
551
|
-
global PROMPT
|
|
552
|
-
if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
|
|
553
|
-
BASH_STATE.shell.sendintr()
|
|
554
|
-
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
555
|
-
if index == 0:
|
|
556
|
-
return True
|
|
557
|
-
before = BASH_STATE.shell.before or ""
|
|
558
|
-
assert before, "Something went wrong updating repl prompt"
|
|
559
|
-
PROMPT = before.split("\n")[-1].strip()
|
|
560
|
-
# Escape all regex
|
|
561
|
-
PROMPT = re.escape(PROMPT)
|
|
562
|
-
console.print(f"Trying to update prompt to: {PROMPT.encode()!r}")
|
|
563
|
-
index = 0
|
|
564
|
-
while index == 0:
|
|
565
|
-
# Consume all REPL prompts till now
|
|
566
|
-
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
567
|
-
console.print(f"Prompt updated to: {PROMPT}")
|
|
568
|
-
return True
|
|
569
|
-
return False
|
|
570
|
-
|
|
571
|
-
|
|
572
578
|
def get_status() -> str:
|
|
573
579
|
status = "\n\n---\n\n"
|
|
574
580
|
if BASH_STATE.state == "pending":
|
|
@@ -576,7 +582,7 @@ def get_status() -> str:
|
|
|
576
582
|
status += "running for = " + BASH_STATE.get_pending_for() + "\n"
|
|
577
583
|
status += "cwd = " + BASH_STATE.cwd + "\n"
|
|
578
584
|
else:
|
|
579
|
-
bg_jobs =
|
|
585
|
+
bg_jobs = BASH_STATE.ensure_env_and_bg_jobs()
|
|
580
586
|
bg_desc = ""
|
|
581
587
|
if bg_jobs and bg_jobs > 0:
|
|
582
588
|
bg_desc = f"; {bg_jobs} background jobs running"
|
|
@@ -646,7 +652,7 @@ def execute_bash(
|
|
|
646
652
|
if isinstance(bash_arg, BashCommand):
|
|
647
653
|
if BASH_STATE.bash_command_mode.allowed_commands == "none":
|
|
648
654
|
return "Error: BashCommand not allowed in current mode", 0.0
|
|
649
|
-
updated_repl_mode = update_repl_prompt(bash_arg.command)
|
|
655
|
+
updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.command)
|
|
650
656
|
if updated_repl_mode:
|
|
651
657
|
BASH_STATE.set_repl()
|
|
652
658
|
response = (
|
|
@@ -723,7 +729,7 @@ def execute_bash(
|
|
|
723
729
|
0.0,
|
|
724
730
|
)
|
|
725
731
|
|
|
726
|
-
updated_repl_mode = update_repl_prompt(bash_arg.send_text)
|
|
732
|
+
updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.send_text)
|
|
727
733
|
if updated_repl_mode:
|
|
728
734
|
BASH_STATE.set_repl()
|
|
729
735
|
response = "Prompt updated, you can execute REPL lines using BashCommand now"
|
|
@@ -739,11 +745,11 @@ def execute_bash(
|
|
|
739
745
|
|
|
740
746
|
except KeyboardInterrupt:
|
|
741
747
|
BASH_STATE.shell.sendintr()
|
|
742
|
-
BASH_STATE.shell.expect(
|
|
748
|
+
BASH_STATE.shell.expect(BASH_STATE.prompt)
|
|
743
749
|
return "---\n\nFailure: user interrupted the execution", 0.0
|
|
744
750
|
|
|
745
751
|
wait = min(timeout_s or TIMEOUT, TIMEOUT_WHILE_OUTPUT)
|
|
746
|
-
index = BASH_STATE.shell.expect([
|
|
752
|
+
index = BASH_STATE.shell.expect([BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait)
|
|
747
753
|
if index == 1:
|
|
748
754
|
text = BASH_STATE.shell.before or ""
|
|
749
755
|
incremental_text = _incremental_text(text, BASH_STATE.pending_output)
|
|
@@ -757,7 +763,9 @@ def execute_bash(
|
|
|
757
763
|
patience -= 1
|
|
758
764
|
itext = incremental_text
|
|
759
765
|
while remaining > 0 and patience > 0:
|
|
760
|
-
index = BASH_STATE.shell.expect(
|
|
766
|
+
index = BASH_STATE.shell.expect(
|
|
767
|
+
[BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait
|
|
768
|
+
)
|
|
761
769
|
if index == 0:
|
|
762
770
|
second_wait_success = True
|
|
763
771
|
break
|
|
@@ -1313,7 +1321,7 @@ def get_tool_output(
|
|
|
1313
1321
|
# At this point we should go into the docker env
|
|
1314
1322
|
res, _ = execute_bash(
|
|
1315
1323
|
enc,
|
|
1316
|
-
BashInteraction(send_text=f"export PS1={
|
|
1324
|
+
BashInteraction(send_text=f"export PS1={BASH_STATE.prompt}"),
|
|
1317
1325
|
None,
|
|
1318
1326
|
0.2,
|
|
1319
1327
|
)
|
|
@@ -1462,7 +1470,7 @@ def read_files(file_paths: list[str], max_tokens: Optional[int]) -> str:
|
|
|
1462
1470
|
if truncated or (max_tokens and max_tokens <= 0):
|
|
1463
1471
|
not_reading = file_paths[i + 1 :]
|
|
1464
1472
|
if not_reading:
|
|
1465
|
-
message += f
|
|
1473
|
+
message += f"\nNot reading the rest of the files: {', '.join(not_reading)} due to token limit, please call again"
|
|
1466
1474
|
break
|
|
1467
1475
|
else:
|
|
1468
1476
|
message += "```"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cli import app
|