wcgw 5.1.1__tar.gz → 5.1.3__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-5.1.1 → wcgw-5.1.3}/PKG-INFO +1 -1
- {wcgw-5.1.1 → wcgw-5.1.3}/pyproject.toml +1 -1
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/bash_state/bash_state.py +39 -37
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/tool_prompts.py +2 -4
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/tools.py +61 -56
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/types_.py +12 -7
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_edit.py +35 -29
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_mcp_server.py +8 -4
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_tools.py +171 -75
- {wcgw-5.1.1 → wcgw-5.1.3}/uv.lock +1 -1
- {wcgw-5.1.1 → wcgw-5.1.3}/.github/workflows/python-publish.yml +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.github/workflows/python-tests.yml +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.github/workflows/python-types.yml +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.gitignore +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.gitmodules +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.python-version +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/.vscode/settings.json +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/CLAUDE.md +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/Dockerfile +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/LICENSE +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/README.md +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/bash_state/parser/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/bash_state/parser/bash_statement_parser.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/common.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/diff-instructions.txt +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/encoder/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/file_ops/diff_edit.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/file_ops/extensions.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/file_ops/search_replace.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/mcp_server/Readme.md +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/mcp_server/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/mcp_server/server.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/memory.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/modes.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/display_tree.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/file_stats.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/path_prob.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/client/repo_ops/repo_context.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw/py.typed +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/__init__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/__main__.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/anthropic_client.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/cli.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/openai_client.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/src/wcgw_cli/openai_utils.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/claude-ss.jpg +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/computer-use.jpg +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/example.jpg +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/rocket-icon.png +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/ss1.png +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/static/workflow-demo.gif +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_bash_parser.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_bash_parser_complex.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_file_range_tracking.py +0 -0
- {wcgw-5.1.1 → wcgw-5.1.3}/tests/test_readfiles.py +0 -0
|
@@ -342,30 +342,30 @@ def get_bash_state_dir_xdg() -> str:
|
|
|
342
342
|
return bash_state_dir
|
|
343
343
|
|
|
344
344
|
|
|
345
|
-
def
|
|
346
|
-
"""Generate a random 4-digit
|
|
345
|
+
def generate_thread_id() -> str:
|
|
346
|
+
"""Generate a random 4-digit thread_id."""
|
|
347
347
|
return f"i{random.randint(1000, 9999)}"
|
|
348
348
|
|
|
349
349
|
|
|
350
|
-
def save_bash_state_by_id(
|
|
351
|
-
"""Save bash state to XDG directory with the given
|
|
352
|
-
if not
|
|
350
|
+
def save_bash_state_by_id(thread_id: str, bash_state_dict: dict[str, Any]) -> None:
|
|
351
|
+
"""Save bash state to XDG directory with the given thread_id."""
|
|
352
|
+
if not thread_id:
|
|
353
353
|
return
|
|
354
354
|
|
|
355
355
|
bash_state_dir = get_bash_state_dir_xdg()
|
|
356
|
-
state_file = os.path.join(bash_state_dir, f"{
|
|
356
|
+
state_file = os.path.join(bash_state_dir, f"{thread_id}_bash_state.json")
|
|
357
357
|
|
|
358
358
|
with open(state_file, "w") as f:
|
|
359
359
|
json.dump(bash_state_dict, f, indent=2)
|
|
360
360
|
|
|
361
361
|
|
|
362
|
-
def load_bash_state_by_id(
|
|
363
|
-
"""Load bash state from XDG directory with the given
|
|
364
|
-
if not
|
|
362
|
+
def load_bash_state_by_id(thread_id: str) -> Optional[dict[str, Any]]:
|
|
363
|
+
"""Load bash state from XDG directory with the given thread_id."""
|
|
364
|
+
if not thread_id:
|
|
365
365
|
return None
|
|
366
366
|
|
|
367
367
|
bash_state_dir = get_bash_state_dir_xdg()
|
|
368
|
-
state_file = os.path.join(bash_state_dir, f"{
|
|
368
|
+
state_file = os.path.join(bash_state_dir, f"{thread_id}_bash_state.json")
|
|
369
369
|
|
|
370
370
|
if not os.path.exists(state_file):
|
|
371
371
|
return None
|
|
@@ -376,7 +376,7 @@ def load_bash_state_by_id(chat_id: str) -> Optional[dict[str, Any]]:
|
|
|
376
376
|
|
|
377
377
|
class BashState:
|
|
378
378
|
_use_screen: bool
|
|
379
|
-
|
|
379
|
+
_current_thread_id: str
|
|
380
380
|
|
|
381
381
|
def __init__(
|
|
382
382
|
self,
|
|
@@ -388,7 +388,7 @@ class BashState:
|
|
|
388
388
|
mode: Optional[Modes],
|
|
389
389
|
use_screen: bool,
|
|
390
390
|
whitelist_for_overwrite: Optional[dict[str, "FileWhitelistData"]] = None,
|
|
391
|
-
|
|
391
|
+
thread_id: Optional[str] = None,
|
|
392
392
|
) -> None:
|
|
393
393
|
self._last_command: str = ""
|
|
394
394
|
self.console = console
|
|
@@ -406,8 +406,10 @@ class BashState:
|
|
|
406
406
|
self._whitelist_for_overwrite: dict[str, FileWhitelistData] = (
|
|
407
407
|
whitelist_for_overwrite or {}
|
|
408
408
|
)
|
|
409
|
-
# Always ensure we have a
|
|
410
|
-
self.
|
|
409
|
+
# Always ensure we have a thread_id
|
|
410
|
+
self._current_thread_id = (
|
|
411
|
+
thread_id if thread_id is not None else generate_thread_id()
|
|
412
|
+
)
|
|
411
413
|
self._bg_expect_thread: Optional[threading.Thread] = None
|
|
412
414
|
self._bg_expect_thread_stop_event = threading.Event()
|
|
413
415
|
self._use_screen = use_screen
|
|
@@ -632,22 +634,22 @@ class BashState:
|
|
|
632
634
|
self._init_shell()
|
|
633
635
|
|
|
634
636
|
@property
|
|
635
|
-
def
|
|
636
|
-
"""Get the current
|
|
637
|
-
return self.
|
|
637
|
+
def current_thread_id(self) -> str:
|
|
638
|
+
"""Get the current thread_id."""
|
|
639
|
+
return self._current_thread_id
|
|
638
640
|
|
|
639
|
-
def
|
|
641
|
+
def load_state_from_thread_id(self, thread_id: str) -> bool:
|
|
640
642
|
"""
|
|
641
|
-
Load bash state from a
|
|
643
|
+
Load bash state from a thread_id.
|
|
642
644
|
|
|
643
645
|
Args:
|
|
644
|
-
|
|
646
|
+
thread_id: The thread_id to load state from
|
|
645
647
|
|
|
646
648
|
Returns:
|
|
647
649
|
bool: True if state was successfully loaded, False otherwise
|
|
648
650
|
"""
|
|
649
651
|
# Try to load state from disk
|
|
650
|
-
loaded_state = load_bash_state_by_id(
|
|
652
|
+
loaded_state = load_bash_state_by_id(thread_id)
|
|
651
653
|
if not loaded_state:
|
|
652
654
|
return False
|
|
653
655
|
|
|
@@ -661,7 +663,7 @@ class BashState:
|
|
|
661
663
|
parsed_state[4],
|
|
662
664
|
parsed_state[5],
|
|
663
665
|
parsed_state[5],
|
|
664
|
-
|
|
666
|
+
thread_id,
|
|
665
667
|
)
|
|
666
668
|
return True
|
|
667
669
|
|
|
@@ -676,13 +678,13 @@ class BashState:
|
|
|
676
678
|
},
|
|
677
679
|
"mode": self._mode,
|
|
678
680
|
"workspace_root": self._workspace_root,
|
|
679
|
-
"chat_id": self.
|
|
681
|
+
"chat_id": self._current_thread_id,
|
|
680
682
|
}
|
|
681
683
|
|
|
682
684
|
def save_state_to_disk(self) -> None:
|
|
683
|
-
"""Save the current bash state to disk using the
|
|
685
|
+
"""Save the current bash state to disk using the thread_id."""
|
|
684
686
|
state_dict = self.serialize()
|
|
685
|
-
save_bash_state_by_id(self.
|
|
687
|
+
save_bash_state_by_id(self._current_thread_id, state_dict)
|
|
686
688
|
|
|
687
689
|
@staticmethod
|
|
688
690
|
def parse_state(
|
|
@@ -721,10 +723,10 @@ class BashState:
|
|
|
721
723
|
for k in whitelist_state
|
|
722
724
|
}
|
|
723
725
|
|
|
724
|
-
# Get the
|
|
725
|
-
|
|
726
|
-
if
|
|
727
|
-
|
|
726
|
+
# Get the thread_id from state, or generate a new one if not present
|
|
727
|
+
thread_id = state.get("chat_id")
|
|
728
|
+
if thread_id is None:
|
|
729
|
+
thread_id = generate_thread_id()
|
|
728
730
|
|
|
729
731
|
return (
|
|
730
732
|
BashCommandMode.deserialize(state["bash_command_mode"]),
|
|
@@ -733,7 +735,7 @@ class BashState:
|
|
|
733
735
|
state["mode"],
|
|
734
736
|
whitelist_dict,
|
|
735
737
|
state.get("workspace_root", ""),
|
|
736
|
-
|
|
738
|
+
thread_id,
|
|
737
739
|
)
|
|
738
740
|
|
|
739
741
|
def load_state(
|
|
@@ -745,7 +747,7 @@ class BashState:
|
|
|
745
747
|
whitelist_for_overwrite: dict[str, "FileWhitelistData"],
|
|
746
748
|
cwd: str,
|
|
747
749
|
workspace_root: str,
|
|
748
|
-
|
|
750
|
+
thread_id: str,
|
|
749
751
|
) -> None:
|
|
750
752
|
"""Create a new BashState instance from a serialized state dictionary"""
|
|
751
753
|
self._bash_command_mode = bash_command_mode
|
|
@@ -755,7 +757,7 @@ class BashState:
|
|
|
755
757
|
self._write_if_empty_mode = write_if_empty_mode
|
|
756
758
|
self._whitelist_for_overwrite = dict(whitelist_for_overwrite)
|
|
757
759
|
self._mode = mode
|
|
758
|
-
self.
|
|
760
|
+
self._current_thread_id = thread_id
|
|
759
761
|
self.reset_shell()
|
|
760
762
|
|
|
761
763
|
# Save state to disk after loading
|
|
@@ -1000,12 +1002,12 @@ def execute_bash(
|
|
|
1000
1002
|
timeout_s: Optional[float],
|
|
1001
1003
|
) -> tuple[str, float]:
|
|
1002
1004
|
try:
|
|
1003
|
-
# Check if the
|
|
1004
|
-
if bash_arg.
|
|
1005
|
-
# Try to load state from the
|
|
1006
|
-
if not bash_state.
|
|
1005
|
+
# Check if the thread_id matches current
|
|
1006
|
+
if bash_arg.thread_id != bash_state.current_thread_id:
|
|
1007
|
+
# Try to load state from the thread_id
|
|
1008
|
+
if not bash_state.load_state_from_thread_id(bash_arg.thread_id):
|
|
1007
1009
|
return (
|
|
1008
|
-
f"Error: No saved bash state found for
|
|
1010
|
+
f"Error: No saved bash state found for thread_id {bash_arg.thread_id}. Please initialize first with this ID.",
|
|
1009
1011
|
0.0,
|
|
1010
1012
|
)
|
|
1011
1013
|
|
|
@@ -74,12 +74,10 @@ TOOL_PROMPTS = [
|
|
|
74
74
|
description="""
|
|
75
75
|
- Writes or edits a file based on the percentage of changes.
|
|
76
76
|
- Use absolute path only (~ allowed).
|
|
77
|
-
- percentage_to_change is calculated as number of existing lines that will have some diff divided by total existing lines.
|
|
78
77
|
- First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change
|
|
79
78
|
- percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced.
|
|
80
|
-
- If percentage_to_change > 50, provide full file content in
|
|
81
|
-
- If percentage_to_change <= 50,
|
|
82
|
-
|
|
79
|
+
- If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks
|
|
80
|
+
- If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks.
|
|
83
81
|
"""
|
|
84
82
|
+ diffinstructions,
|
|
85
83
|
annotations=ToolAnnotations(
|
|
@@ -31,7 +31,7 @@ from syntax_checker import check_syntax
|
|
|
31
31
|
from ..client.bash_state.bash_state import (
|
|
32
32
|
BashState,
|
|
33
33
|
execute_bash,
|
|
34
|
-
|
|
34
|
+
generate_thread_id,
|
|
35
35
|
get_status,
|
|
36
36
|
)
|
|
37
37
|
from ..client.repo_ops.file_stats import (
|
|
@@ -103,7 +103,7 @@ def initialize(
|
|
|
103
103
|
coding_max_tokens: Optional[int],
|
|
104
104
|
noncoding_max_tokens: Optional[int],
|
|
105
105
|
mode: ModesConfig,
|
|
106
|
-
|
|
106
|
+
thread_id: str,
|
|
107
107
|
) -> tuple[str, Context, dict[str, list[tuple[int, int]]]]:
|
|
108
108
|
# Expand the workspace path
|
|
109
109
|
any_workspace_path = expand_user(any_workspace_path)
|
|
@@ -113,16 +113,16 @@ def initialize(
|
|
|
113
113
|
loaded_state = None
|
|
114
114
|
|
|
115
115
|
# For workspace/mode changes, ensure we're using an existing state if possible
|
|
116
|
-
if type != "first_call" and
|
|
117
|
-
# Try to load state from the
|
|
118
|
-
if not context.bash_state.
|
|
116
|
+
if type != "first_call" and thread_id != context.bash_state.current_thread_id:
|
|
117
|
+
# Try to load state from the thread_id
|
|
118
|
+
if not context.bash_state.load_state_from_thread_id(thread_id):
|
|
119
119
|
return (
|
|
120
|
-
f"Error: No saved bash state found for
|
|
120
|
+
f"Error: No saved bash state found for thread_id {thread_id}. Please re-initialize to get a new id or use correct id.",
|
|
121
121
|
context,
|
|
122
122
|
{},
|
|
123
123
|
)
|
|
124
124
|
del (
|
|
125
|
-
|
|
125
|
+
thread_id
|
|
126
126
|
) # No use other than loading correct state before doing actual tool related stuff
|
|
127
127
|
|
|
128
128
|
# Handle task resumption - this applies only to first_call
|
|
@@ -181,10 +181,10 @@ def initialize(
|
|
|
181
181
|
workspace_root = (
|
|
182
182
|
str(folder_to_start) if folder_to_start else parsed_state[5]
|
|
183
183
|
)
|
|
184
|
-
|
|
184
|
+
loaded_thread_id = parsed_state[6] if len(parsed_state) > 6 else None
|
|
185
185
|
|
|
186
|
-
if not
|
|
187
|
-
|
|
186
|
+
if not loaded_thread_id:
|
|
187
|
+
loaded_thread_id = context.bash_state.current_thread_id
|
|
188
188
|
|
|
189
189
|
if mode == "wcgw":
|
|
190
190
|
context.bash_state.load_state(
|
|
@@ -195,7 +195,7 @@ def initialize(
|
|
|
195
195
|
{**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
|
|
196
196
|
str(folder_to_start) if folder_to_start else workspace_root,
|
|
197
197
|
workspace_root,
|
|
198
|
-
|
|
198
|
+
loaded_thread_id,
|
|
199
199
|
)
|
|
200
200
|
else:
|
|
201
201
|
state = modes_to_state(mode)
|
|
@@ -207,7 +207,7 @@ def initialize(
|
|
|
207
207
|
{**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
|
|
208
208
|
str(folder_to_start) if folder_to_start else workspace_root,
|
|
209
209
|
workspace_root,
|
|
210
|
-
|
|
210
|
+
loaded_thread_id,
|
|
211
211
|
)
|
|
212
212
|
except ValueError:
|
|
213
213
|
context.console.print(traceback.format_exc())
|
|
@@ -217,10 +217,10 @@ def initialize(
|
|
|
217
217
|
else:
|
|
218
218
|
mode_changed = is_mode_change(mode, context.bash_state)
|
|
219
219
|
state = modes_to_state(mode)
|
|
220
|
-
|
|
220
|
+
new_thread_id = context.bash_state.current_thread_id
|
|
221
221
|
if type == "first_call":
|
|
222
|
-
# Recreate
|
|
223
|
-
|
|
222
|
+
# Recreate thread_id
|
|
223
|
+
new_thread_id = generate_thread_id()
|
|
224
224
|
# Use the provided workspace path as the workspace root
|
|
225
225
|
context.bash_state.load_state(
|
|
226
226
|
state[0],
|
|
@@ -230,7 +230,7 @@ def initialize(
|
|
|
230
230
|
dict(context.bash_state.whitelist_for_overwrite),
|
|
231
231
|
str(folder_to_start) if folder_to_start else "",
|
|
232
232
|
str(folder_to_start) if folder_to_start else "",
|
|
233
|
-
|
|
233
|
+
new_thread_id,
|
|
234
234
|
)
|
|
235
235
|
if type == "first_call" or mode_changed:
|
|
236
236
|
mode_prompt = get_mode_prompt(context)
|
|
@@ -273,6 +273,8 @@ def initialize(
|
|
|
273
273
|
uname_machine = os.uname().machine
|
|
274
274
|
|
|
275
275
|
output = f"""
|
|
276
|
+
Use thread_id={context.bash_state.current_thread_id} for all wcgw tool calls which take that.
|
|
277
|
+
---
|
|
276
278
|
{mode_prompt}
|
|
277
279
|
|
|
278
280
|
# Environment
|
|
@@ -281,17 +283,16 @@ Machine: {uname_machine}
|
|
|
281
283
|
Initialized in directory (also cwd): {context.bash_state.cwd}
|
|
282
284
|
User home directory: {expanduser("~")}
|
|
283
285
|
|
|
284
|
-
{repo_context}
|
|
285
|
-
|
|
286
286
|
{alignment_context}
|
|
287
|
-
{
|
|
287
|
+
{repo_context}
|
|
288
288
|
|
|
289
289
|
---
|
|
290
290
|
|
|
291
291
|
{memory}
|
|
292
|
-
|
|
293
292
|
---
|
|
294
|
-
|
|
293
|
+
|
|
294
|
+
{initial_files_context}
|
|
295
|
+
|
|
295
296
|
"""
|
|
296
297
|
|
|
297
298
|
return output, context, initial_paths_with_ranges
|
|
@@ -313,13 +314,13 @@ def reset_wcgw(
|
|
|
313
314
|
starting_directory: str,
|
|
314
315
|
mode_name: Optional[Modes],
|
|
315
316
|
change_mode: ModesConfig,
|
|
316
|
-
|
|
317
|
+
thread_id: str,
|
|
317
318
|
) -> str:
|
|
318
|
-
# Load state for this
|
|
319
|
-
if
|
|
320
|
-
# Try to load state from the
|
|
321
|
-
if not context.bash_state.
|
|
322
|
-
return f"Error: No saved bash state found for
|
|
319
|
+
# Load state for this thread_id before proceeding with mode/directory changes
|
|
320
|
+
if thread_id != context.bash_state.current_thread_id:
|
|
321
|
+
# Try to load state from the thread_id
|
|
322
|
+
if not context.bash_state.load_state_from_thread_id(thread_id):
|
|
323
|
+
return f"Error: No saved bash state found for thread_id {thread_id}. Please re-initialize to get a new id or use correct id."
|
|
323
324
|
if mode_name:
|
|
324
325
|
# update modes if they're relative
|
|
325
326
|
if isinstance(change_mode, CodeWriterMode):
|
|
@@ -332,7 +333,7 @@ def reset_wcgw(
|
|
|
332
333
|
change_mode
|
|
333
334
|
)
|
|
334
335
|
|
|
335
|
-
# Reset shell with new mode, using the provided
|
|
336
|
+
# Reset shell with new mode, using the provided thread_id
|
|
336
337
|
context.bash_state.load_state(
|
|
337
338
|
bash_command_mode,
|
|
338
339
|
file_edit_mode,
|
|
@@ -341,7 +342,7 @@ def reset_wcgw(
|
|
|
341
342
|
dict(context.bash_state.whitelist_for_overwrite),
|
|
342
343
|
starting_directory,
|
|
343
344
|
starting_directory,
|
|
344
|
-
|
|
345
|
+
thread_id,
|
|
345
346
|
)
|
|
346
347
|
mode_prompt = get_mode_prompt(context)
|
|
347
348
|
return (
|
|
@@ -357,7 +358,7 @@ def reset_wcgw(
|
|
|
357
358
|
write_if_empty_mode = context.bash_state.write_if_empty_mode
|
|
358
359
|
mode = context.bash_state.mode
|
|
359
360
|
|
|
360
|
-
# Reload state with new directory, using the provided
|
|
361
|
+
# Reload state with new directory, using the provided thread_id
|
|
361
362
|
context.bash_state.load_state(
|
|
362
363
|
bash_command_mode,
|
|
363
364
|
file_edit_mode,
|
|
@@ -366,7 +367,7 @@ def reset_wcgw(
|
|
|
366
367
|
dict(context.bash_state.whitelist_for_overwrite),
|
|
367
368
|
starting_directory,
|
|
368
369
|
starting_directory,
|
|
369
|
-
|
|
370
|
+
thread_id,
|
|
370
371
|
)
|
|
371
372
|
return "Reset successful" + get_status(context.bash_state)
|
|
372
373
|
|
|
@@ -475,7 +476,7 @@ def get_context_for_errors(
|
|
|
475
476
|
ntokens = len(default_enc.encoder(context))
|
|
476
477
|
if ntokens > max_tokens:
|
|
477
478
|
return "Please re-read the file to understand the context"
|
|
478
|
-
return f"Here's relevant snippet from the file where the syntax errors occured:\n
|
|
479
|
+
return f"Here's relevant snippet from the file where the syntax errors occured:\n<snippet>\n{context}\n</snippet>"
|
|
479
480
|
|
|
480
481
|
|
|
481
482
|
def write_file(
|
|
@@ -556,7 +557,7 @@ def write_file(
|
|
|
556
557
|
return (
|
|
557
558
|
(
|
|
558
559
|
msg
|
|
559
|
-
+ f"Here's the existing file:\n
|
|
560
|
+
+ f"Here's the existing file:\n<wcgw:file>\n{file_content_str}\n{final_message}\n</wcgw:file>"
|
|
560
561
|
),
|
|
561
562
|
{path_: file_ranges},
|
|
562
563
|
)
|
|
@@ -578,7 +579,7 @@ def write_file(
|
|
|
578
579
|
return (
|
|
579
580
|
(
|
|
580
581
|
msg
|
|
581
|
-
+ f"Here's the existing file:\n
|
|
582
|
+
+ f"Here's the existing file:\n<wcgw:file>\n{file_content_str}\n</wcgw:file>\n{final_message}"
|
|
582
583
|
),
|
|
583
584
|
{path_: file_ranges},
|
|
584
585
|
)
|
|
@@ -819,12 +820,14 @@ def file_writing(
|
|
|
819
820
|
If percentage_changed > 50%, treat content as direct file content.
|
|
820
821
|
Otherwise, treat content as search/replace blocks.
|
|
821
822
|
"""
|
|
822
|
-
# Check if the
|
|
823
|
-
if file_writing_args.
|
|
824
|
-
# Try to load state from the
|
|
825
|
-
if not context.bash_state.
|
|
823
|
+
# Check if the thread_id matches current
|
|
824
|
+
if file_writing_args.thread_id != context.bash_state.current_thread_id:
|
|
825
|
+
# Try to load state from the thread_id
|
|
826
|
+
if not context.bash_state.load_state_from_thread_id(
|
|
827
|
+
file_writing_args.thread_id
|
|
828
|
+
):
|
|
826
829
|
return (
|
|
827
|
-
f"Error: No saved bash state found for
|
|
830
|
+
f"Error: No saved bash state found for thread_id {file_writing_args.thread_id}. Please re-initialize to get a new id or use correct id.",
|
|
828
831
|
{},
|
|
829
832
|
)
|
|
830
833
|
|
|
@@ -837,14 +840,14 @@ def file_writing(
|
|
|
837
840
|
)
|
|
838
841
|
|
|
839
842
|
# If file doesn't exist, always use direct file_content mode
|
|
840
|
-
content = file_writing_args.
|
|
843
|
+
content = file_writing_args.text_or_search_replace_blocks
|
|
841
844
|
|
|
842
845
|
if not _is_edit(content, file_writing_args.percentage_to_change):
|
|
843
846
|
# Use direct content mode (same as WriteIfEmpty)
|
|
844
847
|
result, paths = write_file(
|
|
845
848
|
WriteIfEmpty(
|
|
846
849
|
file_path=path_,
|
|
847
|
-
file_content=file_writing_args.
|
|
850
|
+
file_content=file_writing_args.text_or_search_replace_blocks,
|
|
848
851
|
),
|
|
849
852
|
True,
|
|
850
853
|
coding_max_tokens,
|
|
@@ -857,7 +860,7 @@ def file_writing(
|
|
|
857
860
|
result, paths = do_diff_edit(
|
|
858
861
|
FileEdit(
|
|
859
862
|
file_path=path_,
|
|
860
|
-
file_edit_using_search_replace_blocks=file_writing_args.
|
|
863
|
+
file_edit_using_search_replace_blocks=file_writing_args.text_or_search_replace_blocks,
|
|
861
864
|
),
|
|
862
865
|
coding_max_tokens,
|
|
863
866
|
noncoding_max_tokens,
|
|
@@ -1012,7 +1015,7 @@ def get_tool_output(
|
|
|
1012
1015
|
)
|
|
1013
1016
|
workspace_path = workspace_path if os.path.exists(workspace_path) else ""
|
|
1014
1017
|
|
|
1015
|
-
# For these specific operations,
|
|
1018
|
+
# For these specific operations, thread_id is required
|
|
1016
1019
|
output = (
|
|
1017
1020
|
reset_wcgw(
|
|
1018
1021
|
context,
|
|
@@ -1021,7 +1024,7 @@ def get_tool_output(
|
|
|
1021
1024
|
if is_mode_change(arg.mode, context.bash_state)
|
|
1022
1025
|
else None,
|
|
1023
1026
|
arg.mode,
|
|
1024
|
-
arg.
|
|
1027
|
+
arg.thread_id,
|
|
1025
1028
|
),
|
|
1026
1029
|
0.0,
|
|
1027
1030
|
)
|
|
@@ -1035,7 +1038,7 @@ def get_tool_output(
|
|
|
1035
1038
|
coding_max_tokens,
|
|
1036
1039
|
noncoding_max_tokens,
|
|
1037
1040
|
arg.mode,
|
|
1038
|
-
arg.
|
|
1041
|
+
arg.thread_id,
|
|
1039
1042
|
)
|
|
1040
1043
|
output = output_, 0.0
|
|
1041
1044
|
# Since init_paths is already a dictionary mapping file paths to line ranges,
|
|
@@ -1164,25 +1167,27 @@ def read_files(
|
|
|
1164
1167
|
continue
|
|
1165
1168
|
|
|
1166
1169
|
if coding_max_tokens:
|
|
1167
|
-
coding_max_tokens = coding_max_tokens - tokens
|
|
1170
|
+
coding_max_tokens = max(0, coding_max_tokens - tokens)
|
|
1168
1171
|
if noncoding_max_tokens:
|
|
1169
|
-
noncoding_max_tokens = noncoding_max_tokens - tokens
|
|
1172
|
+
noncoding_max_tokens = max(0, noncoding_max_tokens - tokens)
|
|
1170
1173
|
|
|
1171
1174
|
range_formatted = range_format(start_line_num, end_line_num)
|
|
1172
|
-
message += f
|
|
1175
|
+
message += f'\n<wcgw:file path="{file}{range_formatted}">\n{content}\n'
|
|
1173
1176
|
|
|
1174
|
-
|
|
1177
|
+
if not truncated:
|
|
1178
|
+
message += "</wcgw:file>"
|
|
1179
|
+
|
|
1180
|
+
# Check if we've hit both token limit
|
|
1175
1181
|
if (
|
|
1176
1182
|
truncated
|
|
1177
1183
|
or (coding_max_tokens is not None and coding_max_tokens <= 0)
|
|
1178
|
-
|
|
1184
|
+
and (noncoding_max_tokens is not None and noncoding_max_tokens <= 0)
|
|
1179
1185
|
):
|
|
1180
1186
|
not_reading = file_paths[i + 1 :]
|
|
1181
1187
|
if not_reading:
|
|
1182
1188
|
message += f"\nNot reading the rest of the files: {', '.join(not_reading)} due to token limit, please call again"
|
|
1183
1189
|
break
|
|
1184
|
-
|
|
1185
|
-
message += "```"
|
|
1190
|
+
|
|
1186
1191
|
return message, file_ranges_dict, truncated
|
|
1187
1192
|
|
|
1188
1193
|
|
|
@@ -1313,7 +1318,7 @@ if __name__ == "__main__":
|
|
|
1313
1318
|
task_id_to_resume="",
|
|
1314
1319
|
mode_name="wcgw",
|
|
1315
1320
|
code_writer_config=None,
|
|
1316
|
-
|
|
1321
|
+
thread_id="",
|
|
1317
1322
|
),
|
|
1318
1323
|
default_enc,
|
|
1319
1324
|
0,
|
|
@@ -1327,7 +1332,7 @@ if __name__ == "__main__":
|
|
|
1327
1332
|
Context(BASH_STATE, BASH_STATE.console),
|
|
1328
1333
|
BashCommand(
|
|
1329
1334
|
action_json=Command(command="pwd"),
|
|
1330
|
-
|
|
1335
|
+
thread_id=BASH_STATE.current_thread_id,
|
|
1331
1336
|
),
|
|
1332
1337
|
default_enc,
|
|
1333
1338
|
0,
|
|
@@ -1357,9 +1362,9 @@ if __name__ == "__main__":
|
|
|
1357
1362
|
Context(BASH_STATE, BASH_STATE.console),
|
|
1358
1363
|
FileWriteOrEdit(
|
|
1359
1364
|
file_path="/Users/arusia/repos/wcgw/src/wcgw/client/tools.py",
|
|
1360
|
-
|
|
1365
|
+
text_or_search_replace_blocks="""test""",
|
|
1361
1366
|
percentage_to_change=100,
|
|
1362
|
-
|
|
1367
|
+
thread_id=BASH_STATE.current_thread_id,
|
|
1363
1368
|
),
|
|
1364
1369
|
default_enc,
|
|
1365
1370
|
0,
|
|
@@ -53,8 +53,8 @@ class Initialize(BaseModel):
|
|
|
53
53
|
initial_files_to_read: list[str]
|
|
54
54
|
task_id_to_resume: str
|
|
55
55
|
mode_name: Literal["wcgw", "architect", "code_writer"]
|
|
56
|
-
|
|
57
|
-
description="Use the
|
|
56
|
+
thread_id: str = Field(
|
|
57
|
+
description="Use the thread_id created in first_call, leave it as empty string if first_call"
|
|
58
58
|
)
|
|
59
59
|
code_writer_config: Optional[CodeWriterMode] = None
|
|
60
60
|
|
|
@@ -105,7 +105,7 @@ class SendAscii(BaseModel):
|
|
|
105
105
|
class BashCommand(BaseModel):
|
|
106
106
|
action_json: Command | StatusCheck | SendText | SendSpecials | SendAscii
|
|
107
107
|
wait_for_seconds: Optional[float] = None
|
|
108
|
-
|
|
108
|
+
thread_id: str
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
class ReadImage(BaseModel):
|
|
@@ -215,10 +215,15 @@ class FileEdit(BaseModel):
|
|
|
215
215
|
|
|
216
216
|
|
|
217
217
|
class FileWriteOrEdit(BaseModel):
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
# Naming should be in sorted order otherwise it gets changed in LLM backend.
|
|
219
|
+
file_path: str = Field(description="#1: absolute file path")
|
|
220
|
+
percentage_to_change: int = Field(
|
|
221
|
+
description="#2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines."
|
|
222
|
+
)
|
|
223
|
+
text_or_search_replace_blocks: str = Field(
|
|
224
|
+
description="#3: content/edit blocks. Must be after #2 in the tool xml"
|
|
225
|
+
)
|
|
226
|
+
thread_id: str = Field(description="#4: thread_id")
|
|
222
227
|
|
|
223
228
|
|
|
224
229
|
class ContextSave(BaseModel):
|