wcgw 2.8.5__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.5 → wcgw-2.8.6}/PKG-INFO +2 -1
- {wcgw-2.8.5 → wcgw-2.8.6}/README.md +1 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/pyproject.toml +1 -1
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/file_ops/diff_edit.py +7 -3
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/file_ops/search_replace.py +50 -9
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/tools.py +92 -81
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/file_ops/test_search_replace.py +73 -61
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_extended.py +51 -31
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_shell.py +73 -73
- {wcgw-2.8.5 → 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.5 → 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.5 → 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_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.5 → wcgw-2.8.6}/tests/client/tools/test_user_interaction.py +21 -15
- wcgw-2.8.6/tests/test_initialize.py +250 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/uv.lock +1 -1
- wcgw-2.8.5/tests/client/tools/test_docker_operations.py +0 -22
- wcgw-2.8.5/tests/client/tools/test_execute_bash.py +0 -71
- wcgw-2.8.5/tests/client/tools/test_large_blocks.py +0 -21
- wcgw-2.8.5/tests/test_initialize.py +0 -281
- {wcgw-2.8.5 → wcgw-2.8.6}/.github/workflows/python-publish.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.github/workflows/python-tests.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.github/workflows/python-types.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.gitignore +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.gitmodules +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.python-version +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/.vscode/settings.json +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/Dockerfile +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/LICENSE +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/gpt_action_json_schema.json +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/gpt_instructions.txt +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/openai.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.git +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.gitignore +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/.python-version +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/LICENSE +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/README.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/RELEASE.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/SECURITY.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/README.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/pyproject.toml +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/mcp_wcgw_fork/uv.lock +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/common.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/computer_use.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/diff-instructions.txt +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/mcp_server/Readme.md +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/mcp_server/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/mcp_server/server.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/memory.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/modes.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/repo_ops/display_tree.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/repo_ops/path_prob.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/repo_ops/repo_context.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/client/sys_utils.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/relay/serve.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/relay/static/privacy.txt +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw/types_.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/__main__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/anthropic_client.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/cli.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/openai_client.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/src/wcgw_cli/openai_utils.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/static/claude-ss.jpg +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/static/computer-use.jpg +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/static/example.jpg +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/static/rocket-icon.png +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/static/ss1.png +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/file_ops/test_diff_edit.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/repo_ops/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/repo_ops/test_path_prob.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/repo_ops/test_repo_context.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_anthropic_client_utils.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_memory.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_openai_utils.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_basic.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_file_ops.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_files.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/test_tools_validation.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/__init__.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_file_operations.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_files/test1.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_files/test2.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_files/test_file.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_full_coverage.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_knowledge_transfer.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_render_terminal.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_terminal_output.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_terminal_output_full.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/client/tools/test_write_file.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/conftest.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_anthropic_client.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_basic.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_common.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_computer_use.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_computer_use_base.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_computer_use_shell.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_sys_utils.py +0 -0
- {wcgw-2.8.5 → wcgw-2.8.6}/tests/test_tools.py +0 -0
|
@@ -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)
|
|
@@ -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)
|
|
@@ -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
|
```
|
|
@@ -130,8 +130,7 @@ def ask_confirmation(prompt: Confirmation) -> str:
|
|
|
130
130
|
return "Yes" if response.lower() == "y" else "No"
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
PROMPT_CONST = "
|
|
134
|
-
PROMPT = PROMPT_CONST
|
|
133
|
+
PROMPT_CONST = "#" + "@wcgw@#"
|
|
135
134
|
|
|
136
135
|
|
|
137
136
|
def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: # type: ignore
|
|
@@ -142,36 +141,36 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
|
|
|
142
141
|
try:
|
|
143
142
|
shell = pexpect.spawn(
|
|
144
143
|
cmd,
|
|
145
|
-
env={**os.environ, **{"PS1":
|
|
144
|
+
env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
|
|
146
145
|
echo=False,
|
|
147
146
|
encoding="utf-8",
|
|
148
147
|
timeout=TIMEOUT,
|
|
149
148
|
cwd=initial_dir,
|
|
150
149
|
)
|
|
151
150
|
shell.sendline(
|
|
152
|
-
f"export PROMPT_COMMAND= PS1={
|
|
151
|
+
f"export PROMPT_COMMAND= PS1={PROMPT_CONST}"
|
|
153
152
|
) # Unset prompt command to avoid interfering
|
|
154
|
-
shell.expect(
|
|
153
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
155
154
|
except Exception as e:
|
|
156
155
|
console.print(traceback.format_exc())
|
|
157
156
|
console.log(f"Error starting shell: {e}. Retrying without rc ...")
|
|
158
157
|
|
|
159
158
|
shell = pexpect.spawn(
|
|
160
159
|
"/bin/bash --noprofile --norc",
|
|
161
|
-
env={**os.environ, **{"PS1":
|
|
160
|
+
env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
|
|
162
161
|
echo=False,
|
|
163
162
|
encoding="utf-8",
|
|
164
163
|
timeout=TIMEOUT,
|
|
165
164
|
)
|
|
166
|
-
shell.sendline(f"export PS1={
|
|
167
|
-
shell.expect(
|
|
165
|
+
shell.sendline(f"export PS1={PROMPT_CONST}")
|
|
166
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
168
167
|
|
|
169
168
|
shell.sendline("stty -icanon -echo")
|
|
170
|
-
shell.expect(
|
|
169
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
171
170
|
shell.sendline("set +o pipefail")
|
|
172
|
-
shell.expect(
|
|
171
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
173
172
|
shell.sendline("export GIT_PAGER=cat PAGER=cat")
|
|
174
|
-
shell.expect(
|
|
173
|
+
shell.expect(PROMPT_CONST, timeout=TIMEOUT)
|
|
175
174
|
return shell
|
|
176
175
|
|
|
177
176
|
|
|
@@ -183,42 +182,6 @@ def _is_int(mystr: str) -> bool:
|
|
|
183
182
|
return False
|
|
184
183
|
|
|
185
184
|
|
|
186
|
-
def _ensure_env_and_bg_jobs(shell: pexpect.spawn) -> Optional[int]: # type: ignore
|
|
187
|
-
if PROMPT != PROMPT_CONST:
|
|
188
|
-
return None
|
|
189
|
-
# First reset the prompt in case venv was sourced or other reasons.
|
|
190
|
-
shell.sendline(f"export PS1={PROMPT}")
|
|
191
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
192
|
-
# Reset echo also if it was enabled
|
|
193
|
-
shell.sendline("stty -icanon -echo")
|
|
194
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
195
|
-
shell.sendline("set +o pipefail")
|
|
196
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
197
|
-
shell.sendline("export GIT_PAGER=cat PAGER=cat")
|
|
198
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
199
|
-
shell.sendline("jobs | wc -l")
|
|
200
|
-
before = ""
|
|
201
|
-
|
|
202
|
-
while not _is_int(before): # Consume all previous output
|
|
203
|
-
try:
|
|
204
|
-
shell.expect(PROMPT, timeout=0.2)
|
|
205
|
-
except pexpect.TIMEOUT:
|
|
206
|
-
console.print(f"Couldn't get exit code, before: {before}")
|
|
207
|
-
raise
|
|
208
|
-
|
|
209
|
-
before_val = shell.before
|
|
210
|
-
if not isinstance(before_val, str):
|
|
211
|
-
before_val = str(before_val)
|
|
212
|
-
assert isinstance(before_val, str)
|
|
213
|
-
before_lines = render_terminal_output(before_val)
|
|
214
|
-
before = "\n".join(before_lines).strip()
|
|
215
|
-
|
|
216
|
-
try:
|
|
217
|
-
return int(before)
|
|
218
|
-
except ValueError:
|
|
219
|
-
raise ValueError(f"Malformed output: {before}")
|
|
220
|
-
|
|
221
|
-
|
|
222
185
|
BASH_CLF_OUTPUT = Literal["repl", "pending"]
|
|
223
186
|
|
|
224
187
|
|
|
@@ -242,7 +205,7 @@ class BashState:
|
|
|
242
205
|
)
|
|
243
206
|
self._mode = mode or Modes.wcgw
|
|
244
207
|
self._whitelist_for_overwrite: set[str] = whitelist_for_overwrite or set()
|
|
245
|
-
|
|
208
|
+
self._prompt = PROMPT_CONST
|
|
246
209
|
self._init_shell()
|
|
247
210
|
|
|
248
211
|
@property
|
|
@@ -261,7 +224,47 @@ class BashState:
|
|
|
261
224
|
def write_if_empty_mode(self) -> WriteIfEmptyMode:
|
|
262
225
|
return self._write_if_empty_mode
|
|
263
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
|
+
|
|
264
266
|
def _init_shell(self) -> None:
|
|
267
|
+
self._prompt = PROMPT_CONST
|
|
265
268
|
self._state: Literal["repl"] | datetime.datetime = "repl"
|
|
266
269
|
self._is_in_docker: Optional[str] = ""
|
|
267
270
|
# Ensure self._cwd exists
|
|
@@ -270,11 +273,11 @@ class BashState:
|
|
|
270
273
|
self._bash_command_mode.bash_mode == "restricted_mode",
|
|
271
274
|
self._cwd,
|
|
272
275
|
)
|
|
273
|
-
|
|
276
|
+
|
|
274
277
|
self._pending_output = ""
|
|
275
278
|
|
|
276
279
|
# Get exit info to ensure shell is ready
|
|
277
|
-
|
|
280
|
+
self.ensure_env_and_bg_jobs()
|
|
278
281
|
|
|
279
282
|
@property
|
|
280
283
|
def shell(self) -> pexpect.spawn: # type: ignore
|
|
@@ -306,9 +309,13 @@ class BashState:
|
|
|
306
309
|
def cwd(self) -> str:
|
|
307
310
|
return self._cwd
|
|
308
311
|
|
|
312
|
+
@property
|
|
313
|
+
def prompt(self) -> str:
|
|
314
|
+
return self._prompt
|
|
315
|
+
|
|
309
316
|
def update_cwd(self) -> str:
|
|
310
317
|
self.shell.sendline("pwd")
|
|
311
|
-
self.shell.expect(
|
|
318
|
+
self.shell.expect(self._prompt, timeout=0.2)
|
|
312
319
|
before_val = self.shell.before
|
|
313
320
|
if not isinstance(before_val, str):
|
|
314
321
|
before_val = str(before_val)
|
|
@@ -388,6 +395,30 @@ class BashState:
|
|
|
388
395
|
def pending_output(self) -> str:
|
|
389
396
|
return self._pending_output
|
|
390
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
|
+
|
|
391
422
|
|
|
392
423
|
BASH_STATE = BashState(os.getcwd(), None, None, None, None)
|
|
393
424
|
INITIALIZED = False
|
|
@@ -544,28 +575,6 @@ WAITING_INPUT_MESSAGE = """A command is already running. NOTE: You can't run mul
|
|
|
544
575
|
"""
|
|
545
576
|
|
|
546
577
|
|
|
547
|
-
def update_repl_prompt(command: str) -> bool:
|
|
548
|
-
global PROMPT
|
|
549
|
-
if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
|
|
550
|
-
BASH_STATE.shell.sendintr()
|
|
551
|
-
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
552
|
-
if index == 0:
|
|
553
|
-
return True
|
|
554
|
-
before = BASH_STATE.shell.before or ""
|
|
555
|
-
assert before, "Something went wrong updating repl prompt"
|
|
556
|
-
PROMPT = before.split("\n")[-1].strip()
|
|
557
|
-
# Escape all regex
|
|
558
|
-
PROMPT = re.escape(PROMPT)
|
|
559
|
-
console.print(f"Trying to update prompt to: {PROMPT.encode()!r}")
|
|
560
|
-
index = 0
|
|
561
|
-
while index == 0:
|
|
562
|
-
# Consume all REPL prompts till now
|
|
563
|
-
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
564
|
-
console.print(f"Prompt updated to: {PROMPT}")
|
|
565
|
-
return True
|
|
566
|
-
return False
|
|
567
|
-
|
|
568
|
-
|
|
569
578
|
def get_status() -> str:
|
|
570
579
|
status = "\n\n---\n\n"
|
|
571
580
|
if BASH_STATE.state == "pending":
|
|
@@ -573,7 +582,7 @@ def get_status() -> str:
|
|
|
573
582
|
status += "running for = " + BASH_STATE.get_pending_for() + "\n"
|
|
574
583
|
status += "cwd = " + BASH_STATE.cwd + "\n"
|
|
575
584
|
else:
|
|
576
|
-
bg_jobs =
|
|
585
|
+
bg_jobs = BASH_STATE.ensure_env_and_bg_jobs()
|
|
577
586
|
bg_desc = ""
|
|
578
587
|
if bg_jobs and bg_jobs > 0:
|
|
579
588
|
bg_desc = f"; {bg_jobs} background jobs running"
|
|
@@ -643,7 +652,7 @@ def execute_bash(
|
|
|
643
652
|
if isinstance(bash_arg, BashCommand):
|
|
644
653
|
if BASH_STATE.bash_command_mode.allowed_commands == "none":
|
|
645
654
|
return "Error: BashCommand not allowed in current mode", 0.0
|
|
646
|
-
updated_repl_mode = update_repl_prompt(bash_arg.command)
|
|
655
|
+
updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.command)
|
|
647
656
|
if updated_repl_mode:
|
|
648
657
|
BASH_STATE.set_repl()
|
|
649
658
|
response = (
|
|
@@ -720,7 +729,7 @@ def execute_bash(
|
|
|
720
729
|
0.0,
|
|
721
730
|
)
|
|
722
731
|
|
|
723
|
-
updated_repl_mode = update_repl_prompt(bash_arg.send_text)
|
|
732
|
+
updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.send_text)
|
|
724
733
|
if updated_repl_mode:
|
|
725
734
|
BASH_STATE.set_repl()
|
|
726
735
|
response = "Prompt updated, you can execute REPL lines using BashCommand now"
|
|
@@ -736,11 +745,11 @@ def execute_bash(
|
|
|
736
745
|
|
|
737
746
|
except KeyboardInterrupt:
|
|
738
747
|
BASH_STATE.shell.sendintr()
|
|
739
|
-
BASH_STATE.shell.expect(
|
|
748
|
+
BASH_STATE.shell.expect(BASH_STATE.prompt)
|
|
740
749
|
return "---\n\nFailure: user interrupted the execution", 0.0
|
|
741
750
|
|
|
742
751
|
wait = min(timeout_s or TIMEOUT, TIMEOUT_WHILE_OUTPUT)
|
|
743
|
-
index = BASH_STATE.shell.expect([
|
|
752
|
+
index = BASH_STATE.shell.expect([BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait)
|
|
744
753
|
if index == 1:
|
|
745
754
|
text = BASH_STATE.shell.before or ""
|
|
746
755
|
incremental_text = _incremental_text(text, BASH_STATE.pending_output)
|
|
@@ -754,7 +763,9 @@ def execute_bash(
|
|
|
754
763
|
patience -= 1
|
|
755
764
|
itext = incremental_text
|
|
756
765
|
while remaining > 0 and patience > 0:
|
|
757
|
-
index = BASH_STATE.shell.expect(
|
|
766
|
+
index = BASH_STATE.shell.expect(
|
|
767
|
+
[BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait
|
|
768
|
+
)
|
|
758
769
|
if index == 0:
|
|
759
770
|
second_wait_success = True
|
|
760
771
|
break
|
|
@@ -1310,7 +1321,7 @@ def get_tool_output(
|
|
|
1310
1321
|
# At this point we should go into the docker env
|
|
1311
1322
|
res, _ = execute_bash(
|
|
1312
1323
|
enc,
|
|
1313
|
-
BashInteraction(send_text=f"export PS1={
|
|
1324
|
+
BashInteraction(send_text=f"export PS1={BASH_STATE.prompt}"),
|
|
1314
1325
|
None,
|
|
1315
1326
|
0.2,
|
|
1316
1327
|
)
|
|
@@ -1459,7 +1470,7 @@ def read_files(file_paths: list[str], max_tokens: Optional[int]) -> str:
|
|
|
1459
1470
|
if truncated or (max_tokens and max_tokens <= 0):
|
|
1460
1471
|
not_reading = file_paths[i + 1 :]
|
|
1461
1472
|
if not_reading:
|
|
1462
|
-
message += f
|
|
1473
|
+
message += f"\nNot reading the rest of the files: {', '.join(not_reading)} due to token limit, please call again"
|
|
1463
1474
|
break
|
|
1464
1475
|
else:
|
|
1465
1476
|
message += "```"
|