wcgw 2.2.0__py3-none-any.whl → 2.2.2__py3-none-any.whl

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.

@@ -169,7 +169,7 @@ def loop(
169
169
  - Do not use interactive commands like nano. Prefer writing simpler commands.
170
170
  - Status of the command and the current working directory will always be returned at the end.
171
171
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
172
- - The first line might be `(...truncated)` if the output is too long.
172
+ - The first or the last line might be `(...truncated)` if the output is too long.
173
173
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
174
174
  - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
175
175
  - Run long running commands in background using screen instead of "&".
@@ -186,7 +186,7 @@ def loop(
186
186
  - Only one of send_text, send_specials, send_ascii should be provided.
187
187
  - This returns within 5 seconds, for heavy programs keep checking status for upto 10 turns before asking user to continue checking again.
188
188
  - Programs don't hang easily, so most likely explanation for no output is usually that the program is still running, and you need to check status again using ["Enter"].
189
-
189
+ - Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
190
190
  """,
191
191
  ),
192
192
  ToolParam(
@@ -222,6 +222,7 @@ def loop(
222
222
  description="""
223
223
  - Use absolute file path only.
224
224
  - Use SEARCH/REPLACE blocks to edit the file.
225
+ - If the edit fails due to block not matching, please retry with correct block till it matches. Re-read the file to ensure you've all the lines correct.
225
226
  """,
226
227
  ),
227
228
  ]
@@ -89,7 +89,7 @@ async def handle_list_tools() -> list[types.Tool]:
89
89
  - Do not use interactive commands like nano. Prefer writing simpler commands.
90
90
  - Status of the command and the current working directory will always be returned at the end.
91
91
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
92
- - The first line might be `(...truncated)` if the output is too long.
92
+ - The first or the last line might be `(...truncated)` if the output is too long.
93
93
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
94
94
  - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
95
95
  - Run long running commands in background using screen instead of "&".
@@ -106,6 +106,7 @@ async def handle_list_tools() -> list[types.Tool]:
106
106
  - Only one of send_text, send_specials, send_ascii should be provided.
107
107
  - This returns within 3 seconds, for heavy programs keep checking status for upto 10 turns before asking user to continue checking again.
108
108
  - Programs don't hang easily, so most likely explanation for no output is usually that the program is still running, and you need to check status again using ["Enter"].
109
+ - Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
109
110
  """,
110
111
  ),
111
112
  ToolParam(
@@ -141,6 +142,7 @@ async def handle_list_tools() -> list[types.Tool]:
141
142
  description="""
142
143
  - Use absolute file path only.
143
144
  - Use SEARCH/REPLACE blocks to edit the file.
145
+ - If the edit fails due to block not matching, please retry with correct block till it matches. Re-read the file to ensure you've all the lines correct.
144
146
  """
145
147
  + diffinstructions,
146
148
  ),
@@ -172,7 +172,7 @@ def loop(
172
172
  - Do not use interactive commands like nano. Prefer writing simpler commands.
173
173
  - Status of the command and the current working directory will always be returned at the end.
174
174
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
175
- - The first line might be `(...truncated)` if the output is too long.
175
+ - The first or the last line might be `(...truncated)` if the output is too long.
176
176
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
177
177
  - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
178
178
  - Run long running commands in background using screen instead of "&".
wcgw/client/tools.py CHANGED
@@ -13,7 +13,7 @@ import threading
13
13
  import importlib.metadata
14
14
  import time
15
15
  import traceback
16
- from tempfile import TemporaryDirectory
16
+ from tempfile import NamedTemporaryFile, TemporaryDirectory
17
17
  from typing import (
18
18
  Callable,
19
19
  Literal,
@@ -412,7 +412,7 @@ def execute_bash(
412
412
  tokens = enc.encode(text)
413
413
 
414
414
  if max_tokens and len(tokens) >= max_tokens:
415
- text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
415
+ text = "(...truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
416
416
 
417
417
  if is_interrupt:
418
418
  text = (
@@ -441,7 +441,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
441
441
 
442
442
  tokens = enc.encode(output)
443
443
  if max_tokens and len(tokens) >= max_tokens:
444
- output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
444
+ output = "(...truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
445
445
 
446
446
  try:
447
447
  exit_status = get_status()
@@ -537,6 +537,15 @@ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
537
537
  else:
538
538
  path_ = writefile.file_path
539
539
 
540
+ error_on_exist = (
541
+ not (
542
+ len(TOOL_CALLS) > 1
543
+ and isinstance(TOOL_CALLS[-2], FileEdit)
544
+ and TOOL_CALLS[-2].file_path == path_
545
+ )
546
+ and error_on_exist
547
+ )
548
+
540
549
  if not BASH_STATE.is_in_docker:
541
550
  if error_on_exist and os.path.exists(path_):
542
551
  file_data = Path(path_).read_text()
@@ -583,7 +592,7 @@ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
583
592
 
584
593
  def find_least_edit_distance_substring(
585
594
  content: str, find_str: str
586
- ) -> tuple[str, float]:
595
+ ) -> tuple[str, str, float]:
587
596
  orig_content_lines = content.split("\n")
588
597
  content_lines = [
589
598
  line.strip() for line in orig_content_lines
@@ -598,11 +607,12 @@ def find_least_edit_distance_substring(
598
607
  content_lines = new_content_lines
599
608
  find_lines = find_str.split("\n")
600
609
  find_lines = [
601
- line.strip() for line in find_lines
610
+ line.strip() for line in find_lines if line.strip()
602
611
  ] # Remove trailing and leading space for calculating edit distance
603
612
  # Slide window and find one with sum of edit distance least
604
613
  min_edit_distance = float("inf")
605
614
  min_edit_distance_lines = []
615
+ context_lines = []
606
616
  for i in range(max(1, len(content_lines) - len(find_lines) + 1)):
607
617
  edit_distance_sum = 0
608
618
  for j in range(len(find_lines)):
@@ -620,19 +630,27 @@ def find_least_edit_distance_substring(
620
630
  + 1
621
631
  )
622
632
  min_edit_distance_lines = orig_content_lines[
633
+ orig_start_index:orig_end_index
634
+ ]
635
+
636
+ context_lines = orig_content_lines[
623
637
  max(0, orig_start_index - 10) : (orig_end_index + 10)
624
638
  ]
625
- return "\n".join(min_edit_distance_lines), min_edit_distance
639
+ return (
640
+ "\n".join(min_edit_distance_lines),
641
+ "\n".join(context_lines),
642
+ min_edit_distance,
643
+ )
626
644
 
627
645
 
628
646
  def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
629
647
  count = content.count(find_lines)
630
648
  if count == 0:
631
- closest_match, min_edit_distance = find_least_edit_distance_substring(
632
- content, find_lines
649
+ closest_match, context_lines, min_edit_distance = (
650
+ find_least_edit_distance_substring(content, find_lines)
633
651
  )
634
652
  if min_edit_distance == 0:
635
- return edit_content(content, closest_match, replace_with_lines)
653
+ return content.replace(closest_match, replace_with_lines, 1)
636
654
  else:
637
655
  print(
638
656
  f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
@@ -640,7 +658,9 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
640
658
  raise Exception(
641
659
  f"""Error: no match found for the provided search block.
642
660
  Requested search block: \n```\n{find_lines}\n```
643
- Possible relevant section in the file:\n---\n```\n{closest_match}\n```\n---\nFile not edited"""
661
+ Possible relevant section in the file:\n---\n```\n{context_lines}\n```\n---\nFile not edited
662
+ \nPlease retry with exact search. Re-read the file if unsure.
663
+ """
644
664
  )
645
665
 
646
666
  content = content.replace(find_lines, replace_with_lines, 1)
@@ -648,6 +668,24 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
648
668
 
649
669
 
650
670
  def do_diff_edit(fedit: FileEdit) -> str:
671
+ try:
672
+ return _do_diff_edit(fedit)
673
+ except Exception as e:
674
+ # Try replacing \"
675
+ try:
676
+ fedit = FileEdit(
677
+ file_path=fedit.file_path,
678
+ file_edit_using_search_replace_blocks=fedit.file_edit_using_search_replace_blocks.replace(
679
+ '\\"', '"'
680
+ ),
681
+ )
682
+ return _do_diff_edit(fedit)
683
+ except Exception:
684
+ pass
685
+ raise e
686
+
687
+
688
+ def _do_diff_edit(fedit: FileEdit) -> str:
651
689
  console.log(f"Editing file: {fedit.file_path}")
652
690
 
653
691
  if not os.path.isabs(fedit.file_path):
@@ -824,70 +862,24 @@ def which_tool_name(name: str) -> Type[TOOLS]:
824
862
  raise ValueError(f"Unknown tool name: {name}")
825
863
 
826
864
 
865
+ TOOL_CALLS: list[TOOLS] = []
866
+
867
+
827
868
  def get_tool_output(
828
- args: dict[object, object]
829
- | Confirmation
830
- | BashCommand
831
- | BashInteraction
832
- | ResetShell
833
- | WriteIfEmpty
834
- | FileEditFindReplace
835
- | FileEdit
836
- | AIAssistant
837
- | DoneFlag
838
- | ReadImage
839
- | Initialize
840
- | ReadFile
841
- | Mouse
842
- | Keyboard
843
- | ScreenShot
844
- | GetScreenInfo,
869
+ args: dict[object, object] | TOOLS,
845
870
  enc: tiktoken.Encoding,
846
871
  limit: float,
847
872
  loop_call: Callable[[str, float], tuple[str, float]],
848
873
  max_tokens: Optional[int],
849
874
  ) -> tuple[list[str | ImageData | DoneFlag], float]:
850
- global IS_IN_DOCKER
875
+ global IS_IN_DOCKER, TOOL_CALLS
851
876
  if isinstance(args, dict):
852
- adapter = TypeAdapter[
853
- Confirmation
854
- | BashCommand
855
- | BashInteraction
856
- | ResetShell
857
- | WriteIfEmpty
858
- | FileEditFindReplace
859
- | FileEdit
860
- | AIAssistant
861
- | DoneFlag
862
- | ReadImage
863
- | ReadFile
864
- | Initialize
865
- | Mouse
866
- | Keyboard
867
- | ScreenShot
868
- | GetScreenInfo,
869
- ](
870
- Confirmation
871
- | BashCommand
872
- | BashInteraction
873
- | ResetShell
874
- | WriteIfEmpty
875
- | FileEditFindReplace
876
- | FileEdit
877
- | AIAssistant
878
- | DoneFlag
879
- | ReadImage
880
- | ReadFile
881
- | Initialize
882
- | Mouse
883
- | Keyboard
884
- | ScreenShot
885
- | GetScreenInfo
886
- )
877
+ adapter = TypeAdapter[TOOLS](TOOLS)
887
878
  arg = adapter.validate_python(args)
888
879
  else:
889
880
  arg = args
890
881
  output: tuple[str | DoneFlag | ImageData, float]
882
+ TOOL_CALLS.append(arg)
891
883
  if isinstance(arg, Confirmation):
892
884
  console.print("Calling ask confirmation tool")
893
885
  output = ask_confirmation(arg), 0.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.2.0
3
+ Version: 2.2.2
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>
@@ -2,21 +2,21 @@ wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
2
2
  wcgw/types_.py,sha256=5QM97K3kOJ7vNBgZ1NYmKPqwSe9WfzDImAIvjbRcClo,1772
3
3
  wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  wcgw/client/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
5
- wcgw/client/anthropic_client.py,sha256=Lfby2Tj4NZn5nrUH6d0VGhdHkD8Z_xk-qtG-S7XB0rc,20664
5
+ wcgw/client/anthropic_client.py,sha256=qa8oLztqOCOQTrQcE1DYMwkXEkLwUdD4FD4DN9GCYTk,20950
6
6
  wcgw/client/cli.py,sha256=-z0kpDAW3mzfQrQeZfaVJhBCAQY3HXnt9GdgQ8s-u0Y,1003
7
7
  wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
8
8
  wcgw/client/computer_use.py,sha256=35NKAlMrxwD0TBlMMRnbCwz4g8TBRGOlcy-cmS-yJ_A,15247
9
9
  wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
10
- wcgw/client/openai_client.py,sha256=xfB96A4qZM7-CUJhyDMqX9rUIzhCw4AtV9ryTKE8SOM,17885
10
+ wcgw/client/openai_client.py,sha256=ByAVGFb1MmBbdMep0vHxH1hypf1WrowAyqlWrvEZq5E,17897
11
11
  wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
12
12
  wcgw/client/sys_utils.py,sha256=GajPntKhaTUMn6EOmopENWZNR2G_BJyuVbuot0x6veI,1376
13
- wcgw/client/tools.py,sha256=fbMzRXudIBwLQwpINEYYx4nBDc5uyu7pSUF3pZUarug,35931
13
+ wcgw/client/tools.py,sha256=kv0ovpulWLq0WIWkEnuHoIQLY3iSk-X3anB2Dy7z0WY,35956
14
14
  wcgw/client/mcp_server/Readme.md,sha256=I8N4dHkTUVGNQ63BQkBMBhCCBTgqGOSF_pUR6iOEiUk,2495
15
15
  wcgw/client/mcp_server/__init__.py,sha256=hyPPwO9cabAJsOMWhKyat9yl7OlSmIobaoAZKHu3DMc,381
16
- wcgw/client/mcp_server/server.py,sha256=VPs6Dm2XHoMmsUKDJlyxh-mJQzqzlrtyviEi_LutjLk,10951
16
+ wcgw/client/mcp_server/server.py,sha256=gp4xXprqZ6X9u5uYmVq2uHOiOIukQGDkgxVsZf_aXmo,11238
17
17
  wcgw/relay/serve.py,sha256=KLYjTvM9CfqdxgFOfHM8LUkFGZ9kKyyJunpNdEIFQUk,8766
18
18
  wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
19
- wcgw-2.2.0.dist-info/METADATA,sha256=bJcxxDqBnsvcO_NKSkJ95wl9Y-EK7P-V_T6XPwoUI2s,7939
20
- wcgw-2.2.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
21
- wcgw-2.2.0.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
22
- wcgw-2.2.0.dist-info/RECORD,,
19
+ wcgw-2.2.2.dist-info/METADATA,sha256=eqZjrD6d3ifTIKW7HuOyHdhyARP-LyEeHMFtJCbqE9s,7939
20
+ wcgw-2.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ wcgw-2.2.2.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
22
+ wcgw-2.2.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any