wcgw 2.7.0__tar.gz → 2.7.1__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.

Files changed (158) hide show
  1. {wcgw-2.7.0 → wcgw-2.7.1}/PKG-INFO +14 -3
  2. {wcgw-2.7.0 → wcgw-2.7.1}/README.md +13 -2
  3. {wcgw-2.7.0 → wcgw-2.7.1}/gpt_action_json_schema.json +43 -63
  4. {wcgw-2.7.0 → wcgw-2.7.1}/gpt_instructions.txt +5 -0
  5. {wcgw-2.7.0 → wcgw-2.7.1}/pyproject.toml +1 -1
  6. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/anthropic_client.py +12 -17
  7. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/mcp_server/server.py +44 -20
  8. wcgw-2.7.1/src/wcgw/client/memory.py +78 -0
  9. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/openai_client.py +13 -17
  10. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/tools.py +34 -26
  11. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/relay/serve.py +7 -7
  12. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/types_.py +3 -7
  13. wcgw-2.7.1/tests/client/test_memory.py +78 -0
  14. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_basic.py +7 -14
  15. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_extended.py +19 -9
  16. wcgw-2.7.1/tests/client/tools/test_docker_operations.py +22 -0
  17. wcgw-2.7.1/tests/client/tools/test_error_handling.py +20 -0
  18. wcgw-2.7.1/tests/client/tools/test_execute_bash.py +71 -0
  19. wcgw-2.7.1/tests/client/tools/test_is_int.py +25 -0
  20. wcgw-2.7.1/tests/client/tools/test_knowledge_transfer.py +155 -0
  21. wcgw-2.7.1/tests/client/tools/test_large_blocks.py +21 -0
  22. wcgw-2.7.1/tests/client/tools/test_render_terminal.py +25 -0
  23. wcgw-2.7.1/tests/client/tools/test_write_file.py +112 -0
  24. wcgw-2.7.1/tests/conftest.py +12 -0
  25. {wcgw-2.7.0 → wcgw-2.7.1}/uv.lock +1 -1
  26. wcgw-2.7.0/src/wcgw/client/memory.py +0 -52
  27. wcgw-2.7.0/tests/client/tools/test_knowledge_transfer.py +0 -322
  28. {wcgw-2.7.0 → wcgw-2.7.1}/.github/workflows/python-publish.yml +0 -0
  29. {wcgw-2.7.0 → wcgw-2.7.1}/.github/workflows/python-tests.yml +0 -0
  30. {wcgw-2.7.0 → wcgw-2.7.1}/.github/workflows/python-types.yml +0 -0
  31. {wcgw-2.7.0 → wcgw-2.7.1}/.gitignore +0 -0
  32. {wcgw-2.7.0 → wcgw-2.7.1}/.gitmodules +0 -0
  33. {wcgw-2.7.0 → wcgw-2.7.1}/.python-version +0 -0
  34. {wcgw-2.7.0 → wcgw-2.7.1}/.vscode/settings.json +0 -0
  35. {wcgw-2.7.0 → wcgw-2.7.1}/LICENSE +0 -0
  36. {wcgw-2.7.0 → wcgw-2.7.1}/openai.md +0 -0
  37. {wcgw-2.7.0 → wcgw-2.7.1}/src/__init__.py +0 -0
  38. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.git +0 -0
  39. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/workflows/main-checks.yml +0 -0
  42. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/workflows/publish-pypi.yml +0 -0
  43. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/workflows/pull-request-checks.yml +0 -0
  44. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.github/workflows/shared.yml +0 -0
  45. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.gitignore +0 -0
  46. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/.python-version +0 -0
  47. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/CODE_OF_CONDUCT.md +0 -0
  48. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/CONTRIBUTING.md +0 -0
  49. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/LICENSE +0 -0
  50. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/README.md +0 -0
  51. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/RELEASE.md +0 -0
  52. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/SECURITY.md +0 -0
  53. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/README.md +0 -0
  54. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/.python-version +0 -0
  55. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/README.md +0 -0
  56. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
  57. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
  58. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
  59. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-prompt/pyproject.toml +0 -0
  60. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/.python-version +0 -0
  61. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/README.md +0 -0
  62. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
  63. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
  64. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
  65. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-resource/pyproject.toml +0 -0
  66. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/.python-version +0 -0
  67. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/README.md +0 -0
  68. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
  69. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
  70. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
  71. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/examples/servers/simple-tool/pyproject.toml +0 -0
  72. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/pyproject.toml +0 -0
  73. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/__init__.py +0 -0
  74. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/client/__init__.py +0 -0
  75. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/client/__main__.py +0 -0
  76. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/client/session.py +0 -0
  77. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/client/sse.py +0 -0
  78. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/client/stdio.py +0 -0
  79. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/py.typed +0 -0
  80. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/__init__.py +0 -0
  81. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/__main__.py +0 -0
  82. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/models.py +0 -0
  83. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/session.py +0 -0
  84. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/sse.py +0 -0
  85. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/stdio.py +0 -0
  86. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/server/websocket.py +0 -0
  87. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/__init__.py +0 -0
  88. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/context.py +0 -0
  89. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/exceptions.py +0 -0
  90. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/memory.py +0 -0
  91. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/progress.py +0 -0
  92. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/session.py +0 -0
  93. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/shared/version.py +0 -0
  94. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/src/mcp_wcgw/types.py +0 -0
  95. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/__init__.py +0 -0
  96. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/client/__init__.py +0 -0
  97. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/client/test_session.py +0 -0
  98. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/client/test_stdio.py +0 -0
  99. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/conftest.py +0 -0
  100. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/server/__init__.py +0 -0
  101. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/server/test_session.py +0 -0
  102. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/server/test_stdio.py +0 -0
  103. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/shared/test_memory.py +0 -0
  104. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/tests/test_types.py +0 -0
  105. {wcgw-2.7.0 → wcgw-2.7.1}/src/mcp_wcgw/uv.lock +0 -0
  106. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/__init__.py +0 -0
  107. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/__init__.py +0 -0
  108. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/__main__.py +0 -0
  109. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/cli.py +0 -0
  110. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/common.py +0 -0
  111. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/computer_use.py +0 -0
  112. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/diff-instructions.txt +0 -0
  113. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/file_ops/diff_edit.py +0 -0
  114. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/file_ops/search_replace.py +0 -0
  115. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/mcp_server/Readme.md +0 -0
  116. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/mcp_server/__init__.py +0 -0
  117. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/openai_utils.py +0 -0
  118. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/repo_ops/display_tree.py +0 -0
  119. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/repo_ops/path_prob.py +0 -0
  120. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
  121. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
  122. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/repo_ops/repo_context.py +0 -0
  123. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/client/sys_utils.py +0 -0
  124. {wcgw-2.7.0 → wcgw-2.7.1}/src/wcgw/relay/static/privacy.txt +0 -0
  125. {wcgw-2.7.0 → wcgw-2.7.1}/static/claude-ss.jpg +0 -0
  126. {wcgw-2.7.0 → wcgw-2.7.1}/static/computer-use.jpg +0 -0
  127. {wcgw-2.7.0 → wcgw-2.7.1}/static/example.jpg +0 -0
  128. {wcgw-2.7.0 → wcgw-2.7.1}/static/rocket-icon.png +0 -0
  129. {wcgw-2.7.0 → wcgw-2.7.1}/static/ss1.png +0 -0
  130. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/file_ops/test_diff_edit.py +0 -0
  131. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/file_ops/test_search_replace.py +0 -0
  132. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/repo_ops/__init__.py +0 -0
  133. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/repo_ops/test_display_tree.py +0 -0
  134. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
  135. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/repo_ops/test_path_prob.py +0 -0
  136. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/repo_ops/test_repo_context.py +0 -0
  137. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_anthropic_client_utils.py +0 -0
  138. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_openai_utils.py +0 -0
  139. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_file_ops.py +0 -0
  140. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_files.py +0 -0
  141. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_shell.py +0 -0
  142. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/test_tools_validation.py +0 -0
  143. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/__init__.py +0 -0
  144. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_command_validation.py +0 -0
  145. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_file_operations.py +0 -0
  146. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_files/test1.py +0 -0
  147. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_files/test2.py +0 -0
  148. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_files/test_file.py +0 -0
  149. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_terminal_output.py +0 -0
  150. {wcgw-2.7.0 → wcgw-2.7.1}/tests/client/tools/test_user_interaction.py +0 -0
  151. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_anthropic_client.py +0 -0
  152. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_basic.py +0 -0
  153. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_common.py +0 -0
  154. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_computer_use.py +0 -0
  155. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_computer_use_base.py +0 -0
  156. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_computer_use_shell.py +0 -0
  157. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_sys_utils.py +0 -0
  158. {wcgw-2.7.0 → wcgw-2.7.1}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.7.0
3
+ Version: 2.7.1
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>
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
33
33
  - Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
34
34
 
35
35
 
36
- ⚠️ Warning: do not use this repo if you aren't scared of "Autonomous shell command execution"
36
+ ⚠️ Warning: do not allow BashCommand tool without reviewing the command, it may result in data loss.
37
37
 
38
38
  [![Tests](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
39
39
  [![Mypy strict](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
@@ -55,7 +55,18 @@ Description-Content-Type: text/markdown
55
55
  - ⚡ **Large file edit**: Supports large file incremental edits to avoid token limit issues. Faster than full file write.
56
56
  - ⚡ **Syntax checking on edits**: Reports feedback to the LLM if its edits have any syntax errors, so that it can redo it.
57
57
  - ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
58
- - ⚡ **Full Shell Access**: No restrictions, complete control.
58
+ - ⚡ **File protections**:
59
+ - The AI needs to read a file at least once before it's allowed to edit or rewrite it. This avoids accidental overwrites.
60
+ - Avoids context filling up while reading very large files. Files get chunked based on token length.
61
+ - On initialisation the provided workspace's directory structure is returned after selecting important files (based on .gitignore as well as a statistical approach)
62
+ - File edit based on search-replace tries to find correct search block if it has multiple matches based on previous search blocks. Fails otherwise (for correctness).
63
+ - File edit has spacing tolerant matching, with warning on issues like indentation mismatch. If there's no match, the closest match is returned to the AI to fix its mistakes.
64
+ - Using Aider-like search and replace, which has better performance than tool call based search and replace.
65
+ - ⚡ **Shell optimisations**:
66
+ - Only one command is allowed to be run at a time, simplifying management and avoiding rogue processes. There's only single shell instance at any point of time.
67
+ - Current working directory is always returned after any shell command to prevent AI from getting lost.
68
+ - Command polling exits after a quick timeout to avoid slow feedback. However, status checking has wait tolerance based on fresh output streaming from a command. Both of these approach combined provides a good shell interaction experience.
69
+
59
70
 
60
71
  ## Top use cases examples
61
72
 
@@ -4,7 +4,7 @@
4
4
  - Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
5
5
 
6
6
 
7
- ⚠️ Warning: do not use this repo if you aren't scared of "Autonomous shell command execution"
7
+ ⚠️ Warning: do not allow BashCommand tool without reviewing the command, it may result in data loss.
8
8
 
9
9
  [![Tests](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
10
10
  [![Mypy strict](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
@@ -26,7 +26,18 @@
26
26
  - ⚡ **Large file edit**: Supports large file incremental edits to avoid token limit issues. Faster than full file write.
27
27
  - ⚡ **Syntax checking on edits**: Reports feedback to the LLM if its edits have any syntax errors, so that it can redo it.
28
28
  - ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
29
- - ⚡ **Full Shell Access**: No restrictions, complete control.
29
+ - ⚡ **File protections**:
30
+ - The AI needs to read a file at least once before it's allowed to edit or rewrite it. This avoids accidental overwrites.
31
+ - Avoids context filling up while reading very large files. Files get chunked based on token length.
32
+ - On initialisation the provided workspace's directory structure is returned after selecting important files (based on .gitignore as well as a statistical approach)
33
+ - File edit based on search-replace tries to find correct search block if it has multiple matches based on previous search blocks. Fails otherwise (for correctness).
34
+ - File edit has spacing tolerant matching, with warning on issues like indentation mismatch. If there's no match, the closest match is returned to the AI to fix its mistakes.
35
+ - Using Aider-like search and replace, which has better performance than tool call based search and replace.
36
+ - ⚡ **Shell optimisations**:
37
+ - Only one command is allowed to be run at a time, simplifying management and avoiding rogue processes. There's only single shell instance at any point of time.
38
+ - Current working directory is always returned after any shell command to prevent AI from getting lost.
39
+ - Command polling exits after a quick timeout to avoid slow feedback. However, status checking has wait tolerance based on fresh output streaming from a command. Both of these approach combined provides a good shell interaction experience.
40
+
30
41
 
31
42
  ## Top use cases examples
32
43
 
@@ -290,16 +290,16 @@
290
290
  }
291
291
  }
292
292
  },
293
- "/v1/knowledge_transfer": {
293
+ "/v1/context_save": {
294
294
  "post": {
295
295
  "x-openai-isConsequential": false,
296
- "summary": "Knowledge Transfer",
297
- "operationId": "knowledge_transfer_v1_knowledge_transfer_post",
296
+ "summary": "Context Save",
297
+ "operationId": "context_save_v1_context_save_post",
298
298
  "requestBody": {
299
299
  "content": {
300
300
  "application/json": {
301
301
  "schema": {
302
- "$ref": "#/components/schemas/KTWithUUID"
302
+ "$ref": "#/components/schemas/ContextSaveWithUUID"
303
303
  }
304
304
  }
305
305
  },
@@ -312,7 +312,7 @@
312
312
  "application/json": {
313
313
  "schema": {
314
314
  "type": "string",
315
- "title": "Response Knowledge Transfer V1 Knowledge Transfer Post"
315
+ "title": "Response Context Save V1 Context Save Post"
316
316
  }
317
317
  }
318
318
  }
@@ -427,6 +427,44 @@
427
427
  ],
428
428
  "title": "CommandWithUUID"
429
429
  },
430
+ "ContextSaveWithUUID": {
431
+ "properties": {
432
+ "id": {
433
+ "type": "string",
434
+ "title": "Id"
435
+ },
436
+ "project_root_path": {
437
+ "type": "string",
438
+ "title": "Project Root Path"
439
+ },
440
+ "description": {
441
+ "type": "string",
442
+ "title": "Description"
443
+ },
444
+ "relevant_file_globs": {
445
+ "items": {
446
+ "type": "string"
447
+ },
448
+ "type": "array",
449
+ "title": "Relevant File Globs"
450
+ },
451
+ "user_id": {
452
+ "type": "string",
453
+ "format": "uuid",
454
+ "title": "User Id"
455
+ }
456
+ },
457
+ "additionalProperties": false,
458
+ "type": "object",
459
+ "required": [
460
+ "id",
461
+ "project_root_path",
462
+ "description",
463
+ "relevant_file_globs",
464
+ "user_id"
465
+ ],
466
+ "title": "ContextSaveWithUUID"
467
+ },
430
468
  "FileEditWithUUID": {
431
469
  "properties": {
432
470
  "file_path": {
@@ -498,64 +536,6 @@
498
536
  ],
499
537
  "title": "InitializeWithUUID"
500
538
  },
501
- "KTWithUUID": {
502
- "properties": {
503
- "id": {
504
- "type": "string",
505
- "title": "Id"
506
- },
507
- "project_root_path": {
508
- "type": "string",
509
- "title": "Project Root Path"
510
- },
511
- "objective": {
512
- "type": "string",
513
- "title": "Objective"
514
- },
515
- "all_user_instructions": {
516
- "type": "string",
517
- "title": "All User Instructions"
518
- },
519
- "current_status_of_the_task": {
520
- "type": "string",
521
- "title": "Current Status Of The Task"
522
- },
523
- "all_issues_snippets": {
524
- "type": "string",
525
- "title": "All Issues Snippets"
526
- },
527
- "relevant_file_paths": {
528
- "items": {
529
- "type": "string"
530
- },
531
- "type": "array",
532
- "title": "Relevant File Paths"
533
- },
534
- "build_and_development_instructions": {
535
- "type": "string",
536
- "title": "Build And Development Instructions"
537
- },
538
- "user_id": {
539
- "type": "string",
540
- "format": "uuid",
541
- "title": "User Id"
542
- }
543
- },
544
- "additionalProperties": false,
545
- "type": "object",
546
- "required": [
547
- "id",
548
- "project_root_path",
549
- "objective",
550
- "all_user_instructions",
551
- "current_status_of_the_task",
552
- "all_issues_snippets",
553
- "relevant_file_paths",
554
- "build_and_development_instructions",
555
- "user_id"
556
- ],
557
- "title": "KTWithUUID"
558
- },
559
539
  "ReadFileWithUUID": {
560
540
  "properties": {
561
541
  "file_paths": {
@@ -44,6 +44,11 @@ Instructions for `BashInteraction`
44
44
  Instructions for `ResetShell`
45
45
  - Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.
46
46
 
47
+ Instructions for `ContextSave`
48
+ - Saves provided description and file contents of all the relevant file paths or globs in a single text file.
49
+ - Provide random unqiue id or whatever user provided.
50
+ - Leave project path as empty string if no project path
51
+
47
52
  Instructions for `FileEdit`:
48
53
  - Use absolute file path only.
49
54
  - Use SEARCH/REPLACE blocks to edit the file.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "2.7.0"
4
+ version = "2.7.1"
5
5
  description = "Shell and coding agent on claude and chatgpt"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.11, <3.13"
@@ -25,10 +25,10 @@ from typer import Typer
25
25
  from ..types_ import (
26
26
  BashCommand,
27
27
  BashInteraction,
28
+ ContextSave,
28
29
  FileEdit,
29
30
  GetScreenInfo,
30
31
  Keyboard,
31
- KnowledgeTransfer,
32
32
  Mouse,
33
33
  ReadFiles,
34
34
  ReadImage,
@@ -130,7 +130,12 @@ def loop(
130
130
  memory = None
131
131
  if resume:
132
132
  try:
133
- memory = load_memory(resume)
133
+ _, memory = load_memory(
134
+ resume,
135
+ 8000,
136
+ lambda x: default_enc.encode(x).ids,
137
+ lambda x: default_enc.decode(x),
138
+ )
134
139
  except OSError:
135
140
  if resume == "latest":
136
141
  resume_path = sorted(Path(".wcgw").iterdir(), key=os.path.getmtime)[-1]
@@ -215,22 +220,12 @@ def loop(
215
220
  """,
216
221
  ),
217
222
  ToolParam(
218
- input_schema=KnowledgeTransfer.model_json_schema(),
219
- name="KnowledgeTransfer",
223
+ input_schema=ContextSave.model_json_schema(),
224
+ name="ContextSave",
220
225
  description="""
221
- Write detailed description in order to do a KT, if the user asks for it.
222
- Save all information necessary for a person to understand the task and the problems.
223
-
224
- - `all_user_instructions` should contain all instructions user shared in the conversation.
225
- - `current_status_of_the_task` should contain only what is already achieved, not what's remaining.
226
- - `all_issues_snippets` should only contain snippets of error, traceback, file snippets, commands, etc., no comments or solutions (important!).
227
- - Be very verbose in `all_issues_snippets` providing as much error context as possible.
228
- - Provide an id if the user hasn't provided one.
229
- - This tool will return a text file path where the information is saved.
230
- - After the tool completes succesfully, tell the user the task id and the generate file path. (important!)
231
- - Leave arguments as empty string if they aren't relevant.
232
- - This tool marks end of your conversation, do not run any further tools after calling this.
233
- - Provide absolute file paths only in `relevant_file_paths` containing all relevant files.
226
+ Saves provided description and file contents of all the relevant file paths or globs in a single text file.
227
+ - Provide random unqiue id or whatever user provided.
228
+ - Leave project path as empty string if no project path
234
229
  """,
235
230
  ),
236
231
  ]
@@ -14,6 +14,7 @@ from mcp_wcgw.types import Tool as ToolParam
14
14
  from ...types_ import (
15
15
  BashCommand,
16
16
  BashInteraction,
17
+ ContextSave,
17
18
  FileEdit,
18
19
  GetScreenInfo,
19
20
  Initialize,
@@ -46,14 +47,47 @@ async def handle_read_resource(uri: AnyUrl) -> str:
46
47
 
47
48
  @server.list_prompts() # type: ignore
48
49
  async def handle_list_prompts() -> list[types.Prompt]:
49
- return []
50
+ return [
51
+ types.Prompt(
52
+ name="KnowledgeTransfer",
53
+ description="Prompt for invoking ContextSave tool in order to do a comprehensive knowledge transfer of a coding task. Prompts to save detailed error log and instructions.",
54
+ )
55
+ ]
50
56
 
51
57
 
52
58
  @server.get_prompt() # type: ignore
53
59
  async def handle_get_prompt(
54
60
  name: str, arguments: dict[str, str] | None
55
61
  ) -> types.GetPromptResult:
56
- return types.GetPromptResult(messages=[])
62
+ messages = []
63
+ if name == "KnowledgeTransfer":
64
+ messages = [
65
+ types.PromptMessage(
66
+ role="user",
67
+ content=types.TextContent(
68
+ type="text",
69
+ text="""Use `ContextSave` tool to do a knowledge transfer of the task in hand.
70
+ Write detailed description in order to do a KT.
71
+ Save all information necessary for a person to understand the task and the problems.
72
+
73
+ Format the `description` field using Markdown with the following sections.
74
+ - "# Objective" section containing project and task objective.
75
+ - "# All user instructions" section should be provided containing all instructions user shared in the conversation.
76
+ - "# Current status of the task" should be provided containing only what is already achieved, not what's remaining.
77
+ - "# All issues with snippets" section containing snippets of error, traceback, file snippets, commands, etc. But no comments or solutions.
78
+ - Be very verbose in the all issues with snippets section providing as much error context as possible.
79
+ - "# Build and development instructions" section containing instructions to build or run project or run tests, or envrionment related information. Only include what's known. Leave empty if unknown.
80
+ - After the tool completes succesfully, tell me the task id and the file path the tool generated (important!)
81
+ - This tool marks end of your conversation, do not run any further tools after calling this.
82
+
83
+ Provide all relevant file paths in order to understand and solve the the task. Err towards providing more file paths than fewer.
84
+
85
+ (Note to self: this conversation can then be resumed later asking "Resume `<generated id>`" which should call Initialize tool)
86
+ """,
87
+ ),
88
+ )
89
+ ]
90
+ return types.GetPromptResult(messages=messages)
57
91
 
58
92
 
59
93
  @server.list_tools() # type: ignore
@@ -153,24 +187,14 @@ async def handle_list_tools() -> list[types.Tool]:
153
187
  """
154
188
  + diffinstructions,
155
189
  ),
156
- # ToolParam(
157
- # inputSchema=KnowledgeTransfer.model_json_schema(),
158
- # name="KnowledgeTransfer",
159
- # description="""
160
- # Write detailed description in order to do a KT, if the user asks for it.
161
- # Save all information necessary for a person to understand the task and the problems.
162
- # - `all_user_instructions` should contain all instructions user shared in the conversation.
163
- # - `current_status_of_the_task` should contain only what is already achieved, not what's remaining.
164
- # - `all_issues_snippets` should only contain snippets of error, traceback, file snippets, commands, etc., no comments or solutions (important!).
165
- # - Be very verbose in `all_issues_snippets` providing as much error context as possible.
166
- # - Provide an id if the user hasn't provided one.
167
- # - This tool will return a text file path where the information is saved.
168
- # - After the tool completes succesfully, tell the user the task id and the generate file path. (important!)
169
- # - Leave arguments as empty string if they aren't relevant.
170
- # - This tool marks end of your conversation, do not run any further tools after calling this.
171
- # - Provide absolute file paths only in `relevant_file_paths` containing all relevant files.
172
- # """,
173
- # ),
190
+ ToolParam(
191
+ inputSchema=ContextSave.model_json_schema(),
192
+ name="ContextSave",
193
+ description="""
194
+ Saves provided description and file contents of all the relevant file paths or globs in a single text file.
195
+ - Provide random unqiue id or whatever user provided.
196
+ - Leave project path as empty string if no project path""",
197
+ ),
174
198
  ]
175
199
  if COMPUTER_USE_ON_DOCKER_ENABLED:
176
200
  tools += [
@@ -0,0 +1,78 @@
1
+ import os
2
+ import re
3
+ import shlex
4
+ from typing import Callable, Optional
5
+
6
+ from ..types_ import ContextSave
7
+
8
+
9
+ def get_app_dir_xdg() -> str:
10
+ xdg_data_dir = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
11
+ return os.path.join(xdg_data_dir, "wcgw")
12
+
13
+
14
+ def format_memory(task_memory: ContextSave, relevant_files: str) -> str:
15
+ memory_data = ""
16
+ if task_memory.project_root_path:
17
+ memory_data += (
18
+ f"# PROJECT ROOT = {shlex.quote(task_memory.project_root_path)}\n"
19
+ )
20
+ memory_data += task_memory.description
21
+
22
+ memory_data += (
23
+ "\n\n"
24
+ + "# Relevant file paths\n"
25
+ + ", ".join(map(shlex.quote, task_memory.relevant_file_globs))
26
+ )
27
+
28
+ memory_data += "\n\n# Relevant Files:\n" + relevant_files
29
+
30
+ return memory_data
31
+
32
+
33
+ def save_memory(task_memory: ContextSave, relevant_files: str) -> str:
34
+ app_dir = get_app_dir_xdg()
35
+ memory_dir = os.path.join(app_dir, "memory")
36
+ os.makedirs(memory_dir, exist_ok=True)
37
+
38
+ task_id = task_memory.id
39
+ if not task_id:
40
+ raise Exception("Task id can not be empty")
41
+ memory_data = format_memory(task_memory, relevant_files)
42
+
43
+ memory_file_full = os.path.join(memory_dir, f"{task_id}.txt")
44
+
45
+ with open(memory_file_full, "w") as f:
46
+ f.write(memory_data)
47
+
48
+ return memory_file_full
49
+
50
+
51
+ def load_memory[T](
52
+ task_id: str,
53
+ max_tokens: Optional[int],
54
+ encoder: Callable[[str], list[T]],
55
+ decoder: Callable[[list[T]], str],
56
+ ) -> tuple[str, str]:
57
+ app_dir = get_app_dir_xdg()
58
+ memory_dir = os.path.join(app_dir, "memory")
59
+ memory_file = os.path.join(memory_dir, f"{task_id}.txt")
60
+
61
+ with open(memory_file, "r") as f:
62
+ data = f.read()
63
+
64
+ if max_tokens:
65
+ toks = encoder(data)
66
+ if len(toks) > max_tokens:
67
+ toks = toks[: max(0, max_tokens - 10)]
68
+ data = decoder(toks)
69
+ data += "\n(... truncated)"
70
+
71
+ project_root_match = re.search(r"# PROJECT ROOT = \s*(.*?)\s*$", data, re.MULTILINE)
72
+ project_root_path = ""
73
+ if project_root_match:
74
+ matched_path = project_root_match.group(1)
75
+ parsed_ = shlex.split(matched_path)
76
+ if parsed_ and len(parsed_) == 1:
77
+ project_root_path = parsed_[0]
78
+ return project_root_path, data
@@ -26,8 +26,8 @@ from typer import Typer
26
26
  from ..types_ import (
27
27
  BashCommand,
28
28
  BashInteraction,
29
+ ContextSave,
29
30
  FileEdit,
30
- KnowledgeTransfer,
31
31
  ReadFiles,
32
32
  ReadImage,
33
33
  ResetShell,
@@ -39,6 +39,7 @@ from .openai_utils import get_input_cost, get_output_cost
39
39
  from .tools import (
40
40
  DoneFlag,
41
41
  ImageData,
42
+ default_enc,
42
43
  get_tool_output,
43
44
  initialize,
44
45
  which_tool,
@@ -124,7 +125,12 @@ def loop(
124
125
  memory = None
125
126
  if resume:
126
127
  try:
127
- memory = load_memory(resume)
128
+ _, memory = load_memory(
129
+ resume,
130
+ 8000,
131
+ lambda x: default_enc.encode(x).ids,
132
+ lambda x: default_enc.decode(x),
133
+ )
128
134
  except OSError:
129
135
  if resume == "latest":
130
136
  resume_path = sorted(Path(".wcgw").iterdir(), key=os.path.getmtime)[-1]
@@ -211,22 +217,12 @@ def loop(
211
217
  description="Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.",
212
218
  ),
213
219
  openai.pydantic_function_tool(
214
- KnowledgeTransfer,
220
+ ContextSave,
215
221
  description="""
216
- Write detailed description in order to do a KT, if the user asks for it.
217
- Save all information necessary for a person to understand the task and the problems.
218
-
219
- - `all_user_instructions` should contain all instructions user shared in the conversation.
220
- - `current_status_of_the_task` should contain only what is already achieved, not what's remaining.
221
- - `all_issues_snippets` should only contain snippets of error, traceback, file snippets, commands, etc., no comments or solutions (important!).
222
- - Be very verbose in `all_issues_snippets` providing as much error context as possible.
223
- - Provide an id if the user hasn't provided one.
224
- - This tool will return a text file path where the information is saved.
225
- - After the tool completes succesfully, tell the user the task id and the generate file path. (important!)
226
- - Leave arguments as empty string if they aren't relevant.
227
- - This tool marks end of your conversation, do not run any further tools after calling this.
228
- - Provide absolute file paths only in `relevant_file_paths` containing all relevant files.
229
- """,
222
+
223
+ Saves provided description and file contents of all the relevant file paths or globs in a single text file.
224
+ - Provide random unqiue id or whatever user provided.
225
+ - Leave project path as empty string if no project path""",
230
226
  ),
231
227
  ]
232
228
 
@@ -1,5 +1,6 @@
1
1
  import base64
2
2
  import datetime
3
+ import glob
3
4
  import importlib.metadata
4
5
  import json
5
6
  import mimetypes
@@ -38,12 +39,12 @@ from websockets.sync.client import connect as syncconnect
38
39
  from ..types_ import (
39
40
  BashCommand,
40
41
  BashInteraction,
42
+ ContextSave,
41
43
  FileEdit,
42
44
  FileEditFindReplace,
43
45
  GetScreenInfo,
44
46
  Initialize,
45
47
  Keyboard,
46
- KnowledgeTransfer,
47
48
  Mouse,
48
49
  ReadFiles,
49
50
  ReadImage,
@@ -53,7 +54,7 @@ from ..types_ import (
53
54
  )
54
55
  from .computer_use import run_computer_tool
55
56
  from .file_ops.search_replace import search_replace_edit
56
- from .memory import format_memory, load_memory, save_memory
57
+ from .memory import load_memory, save_memory
57
58
  from .repo_ops.repo_context import get_repo_context
58
59
  from .sys_utils import command_run
59
60
 
@@ -307,16 +308,17 @@ def initialize(
307
308
  memory = ""
308
309
  if task_id_to_resume:
309
310
  try:
310
- task_mem = load_memory(task_id_to_resume)
311
- mem_files = task_mem.relevant_file_paths
312
- mem_files_read = read_files(mem_files, max_tokens)
313
- memory = "Following is the retrieved task:\n" + format_memory(
314
- task_mem, mem_files_read
311
+ project_root_path, task_mem = load_memory(
312
+ task_id_to_resume,
313
+ max_tokens,
314
+ lambda x: default_enc.encode(x).ids,
315
+ lambda x: default_enc.decode(x),
315
316
  )
317
+ memory = "Following is the retrieved task:\n" + task_mem
316
318
  if (
317
319
  not any_workspace_path or not os.path.exists(any_workspace_path)
318
- ) and os.path.exists(task_mem.project_root_path):
319
- any_workspace_path = task_mem.project_root_path
320
+ ) and os.path.exists(project_root_path):
321
+ any_workspace_path = project_root_path
320
322
  except Exception:
321
323
  memory = f'Error: Unable to load task with ID "{task_id_to_resume}" '
322
324
 
@@ -1003,7 +1005,7 @@ TOOLS = (
1003
1005
  | Keyboard
1004
1006
  | ScreenShot
1005
1007
  | GetScreenInfo
1006
- | KnowledgeTransfer
1008
+ | ContextSave
1007
1009
  )
1008
1010
 
1009
1011
 
@@ -1045,8 +1047,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
1045
1047
  return ScreenShot
1046
1048
  elif name == "GetScreenInfo":
1047
1049
  return GetScreenInfo
1048
- elif name == "KnowledgeTransfer":
1049
- return KnowledgeTransfer
1050
+ elif name == "ContextSave":
1051
+ return ContextSave
1050
1052
  else:
1051
1053
  raise ValueError(f"Unknown tool name: {name}")
1052
1054
 
@@ -1142,20 +1144,26 @@ def get_tool_output(
1142
1144
  )
1143
1145
  BASH_STATE.set_in_docker(arg.docker_image_id)
1144
1146
  return outputs, outputs_cost[1]
1145
- elif isinstance(arg, KnowledgeTransfer):
1147
+ elif isinstance(arg, ContextSave):
1146
1148
  console.print("Calling task memory tool")
1147
- relevant_files = arg.relevant_file_paths
1148
- for i, fpath in enumerate(relevant_files):
1149
- if not os.path.isabs(fpath):
1150
- relpath = os.path.join(arg.project_root_path, fpath)
1151
- if os.path.exists(relpath):
1152
- relevant_files[i] = relpath
1153
- else:
1154
- raise Exception(f"The file path {fpath} does not exist")
1155
- elif not os.path.exists(fpath):
1156
- raise Exception(f"The file path {fpath} does not exist")
1157
- relevant_files_data = read_files(relevant_files, None)
1158
- output = save_memory(arg, relevant_files_data), 0.0
1149
+ assert not BASH_STATE.is_in_docker, "KT not supported in docker"
1150
+ relevant_files = []
1151
+ warnings = ""
1152
+ for fglob in arg.relevant_file_globs:
1153
+ fglob = expand_user(fglob, None)
1154
+ if not os.path.isabs(fglob) and arg.project_root_path:
1155
+ fglob = os.path.join(arg.project_root_path, fglob)
1156
+ globs = glob.glob(fglob)
1157
+ relevant_files.extend(globs[:1000])
1158
+ if not globs:
1159
+ warnings += f"Warning: No files found for the glob: {fglob}\n"
1160
+ relevant_files_data = read_files(relevant_files[:10_000], None)
1161
+ output_ = save_memory(arg, relevant_files_data)
1162
+ if not relevant_files and arg.relevant_file_globs:
1163
+ output_ = f'Error: No files found for the given globs. Context file successfully saved at "{output_}", but please fix the error.'
1164
+ elif warnings:
1165
+ output_ = warnings + "\nContext file successfully saved at " + output_
1166
+ output = output_, 0.0
1159
1167
  else:
1160
1168
  raise ValueError(f"Unknown tool: {arg}")
1161
1169
  if isinstance(output[0], str):
@@ -1184,7 +1192,7 @@ class Mdata(BaseModel):
1184
1192
  | str
1185
1193
  | ReadFiles
1186
1194
  | Initialize
1187
- | KnowledgeTransfer
1195
+ | ContextSave
1188
1196
  )
1189
1197
 
1190
1198