wcgw 5.1.2__py3-none-any.whl → 5.2.0__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.

@@ -342,30 +342,30 @@ def get_bash_state_dir_xdg() -> str:
342
342
  return bash_state_dir
343
343
 
344
344
 
345
- def generate_chat_id() -> str:
346
- """Generate a random 4-digit chat ID."""
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(chat_id: str, bash_state_dict: dict[str, Any]) -> None:
351
- """Save bash state to XDG directory with the given chat ID."""
352
- if not chat_id:
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"{chat_id}_bash_state.json")
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(chat_id: str) -> Optional[dict[str, Any]]:
363
- """Load bash state from XDG directory with the given chat ID."""
364
- if not chat_id:
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"{chat_id}_bash_state.json")
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
- _current_chat_id: str
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
- chat_id: Optional[str] = None,
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 chat ID
410
- self._current_chat_id = chat_id if chat_id is not None else generate_chat_id()
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 current_chat_id(self) -> str:
636
- """Get the current chat ID."""
637
- return self._current_chat_id
637
+ def current_thread_id(self) -> str:
638
+ """Get the current thread_id."""
639
+ return self._current_thread_id
638
640
 
639
- def load_state_from_chat_id(self, chat_id: str) -> bool:
641
+ def load_state_from_thread_id(self, thread_id: str) -> bool:
640
642
  """
641
- Load bash state from a chat ID.
643
+ Load bash state from a thread_id.
642
644
 
643
645
  Args:
644
- chat_id: The chat ID to load state from
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(chat_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
- chat_id,
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._current_chat_id,
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 chat ID."""
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._current_chat_id, state_dict)
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 chat_id from state, or generate a new one if not present
725
- chat_id = state.get("chat_id")
726
- if chat_id is None:
727
- chat_id = generate_chat_id()
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
- chat_id,
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
- chat_id: str,
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._current_chat_id = chat_id
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 chat ID matches current
1004
- if bash_arg.chat_id != bash_state.current_chat_id:
1005
- # Try to load state from the chat ID
1006
- if not bash_state.load_state_from_chat_id(bash_arg.chat_id):
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 chat ID {bash_arg.chat_id}. Please initialize first with this ID.",
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
 
@@ -4,7 +4,7 @@ from typing import Callable, Optional
4
4
  from .diff_edit import FileEditInput, FileEditOutput, SearchReplaceMatchError
5
5
 
6
6
  # Global regex patterns
7
- SEARCH_MARKER = re.compile(r"^<<<<<<+\s*SEARCH\s*$")
7
+ SEARCH_MARKER = re.compile(r"^<<<<<<+\s*SEARCH>?\s*$")
8
8
  DIVIDER_MARKER = re.compile(r"^======*\s*$")
9
9
  REPLACE_MARKER = re.compile(r"^>>>>>>+\s*REPLACE\s*$")
10
10
 
@@ -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 file_content_or_search_replace_blocks
81
- - If percentage_to_change <= 50, file_content_or_search_replace_blocks should be search/replace blocks.
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(
wcgw/client/tools.py CHANGED
@@ -26,12 +26,13 @@ from openai.types.chat import (
26
26
  ChatCompletionMessageParam,
27
27
  )
28
28
  from pydantic import BaseModel, TypeAdapter, ValidationError
29
- from syntax_checker import check_syntax
29
+ from syntax_checker import Output as SCOutput
30
+ from syntax_checker import check_syntax as raw_check_syntax
30
31
 
31
32
  from ..client.bash_state.bash_state import (
32
33
  BashState,
33
34
  execute_bash,
34
- generate_chat_id,
35
+ generate_thread_id,
35
36
  get_status,
36
37
  )
37
38
  from ..client.repo_ops.file_stats import (
@@ -78,6 +79,13 @@ class Context:
78
79
  console: Console
79
80
 
80
81
 
82
+ def check_syntax(ext: str, content: str) -> SCOutput:
83
+ if ext == "html":
84
+ # Ignore due to prevelance of templating, causing issues
85
+ return raw_check_syntax("html", "")
86
+ return raw_check_syntax(ext, content)
87
+
88
+
81
89
  def get_mode_prompt(context: Context) -> str:
82
90
  mode_prompt = ""
83
91
  if context.bash_state.mode == "code_writer":
@@ -103,7 +111,7 @@ def initialize(
103
111
  coding_max_tokens: Optional[int],
104
112
  noncoding_max_tokens: Optional[int],
105
113
  mode: ModesConfig,
106
- chat_id: str,
114
+ thread_id: str,
107
115
  ) -> tuple[str, Context, dict[str, list[tuple[int, int]]]]:
108
116
  # Expand the workspace path
109
117
  any_workspace_path = expand_user(any_workspace_path)
@@ -113,16 +121,16 @@ def initialize(
113
121
  loaded_state = None
114
122
 
115
123
  # For workspace/mode changes, ensure we're using an existing state if possible
116
- if type != "first_call" and chat_id != context.bash_state.current_chat_id:
117
- # Try to load state from the chat ID
118
- if not context.bash_state.load_state_from_chat_id(chat_id):
124
+ if type != "first_call" and thread_id != context.bash_state.current_thread_id:
125
+ # Try to load state from the thread_id
126
+ if not context.bash_state.load_state_from_thread_id(thread_id):
119
127
  return (
120
- f"Error: No saved bash state found for chat ID {chat_id}. Please re-initialize to get a new id or use correct id.",
128
+ 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
129
  context,
122
130
  {},
123
131
  )
124
132
  del (
125
- chat_id
133
+ thread_id
126
134
  ) # No use other than loading correct state before doing actual tool related stuff
127
135
 
128
136
  # Handle task resumption - this applies only to first_call
@@ -181,10 +189,10 @@ def initialize(
181
189
  workspace_root = (
182
190
  str(folder_to_start) if folder_to_start else parsed_state[5]
183
191
  )
184
- loaded_chat_id = parsed_state[6] if len(parsed_state) > 6 else None
192
+ loaded_thread_id = parsed_state[6] if len(parsed_state) > 6 else None
185
193
 
186
- if not loaded_chat_id:
187
- loaded_chat_id = context.bash_state.current_chat_id
194
+ if not loaded_thread_id:
195
+ loaded_thread_id = context.bash_state.current_thread_id
188
196
 
189
197
  if mode == "wcgw":
190
198
  context.bash_state.load_state(
@@ -195,7 +203,7 @@ def initialize(
195
203
  {**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
196
204
  str(folder_to_start) if folder_to_start else workspace_root,
197
205
  workspace_root,
198
- loaded_chat_id,
206
+ loaded_thread_id,
199
207
  )
200
208
  else:
201
209
  state = modes_to_state(mode)
@@ -207,7 +215,7 @@ def initialize(
207
215
  {**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
208
216
  str(folder_to_start) if folder_to_start else workspace_root,
209
217
  workspace_root,
210
- loaded_chat_id,
218
+ loaded_thread_id,
211
219
  )
212
220
  except ValueError:
213
221
  context.console.print(traceback.format_exc())
@@ -217,10 +225,10 @@ def initialize(
217
225
  else:
218
226
  mode_changed = is_mode_change(mode, context.bash_state)
219
227
  state = modes_to_state(mode)
220
- new_chat_id = context.bash_state.current_chat_id
228
+ new_thread_id = context.bash_state.current_thread_id
221
229
  if type == "first_call":
222
- # Recreate chat id
223
- new_chat_id = generate_chat_id()
230
+ # Recreate thread_id
231
+ new_thread_id = generate_thread_id()
224
232
  # Use the provided workspace path as the workspace root
225
233
  context.bash_state.load_state(
226
234
  state[0],
@@ -230,7 +238,7 @@ def initialize(
230
238
  dict(context.bash_state.whitelist_for_overwrite),
231
239
  str(folder_to_start) if folder_to_start else "",
232
240
  str(folder_to_start) if folder_to_start else "",
233
- new_chat_id,
241
+ new_thread_id,
234
242
  )
235
243
  if type == "first_call" or mode_changed:
236
244
  mode_prompt = get_mode_prompt(context)
@@ -273,6 +281,8 @@ def initialize(
273
281
  uname_machine = os.uname().machine
274
282
 
275
283
  output = f"""
284
+ Use thread_id={context.bash_state.current_thread_id} for all wcgw tool calls which take that.
285
+ ---
276
286
  {mode_prompt}
277
287
 
278
288
  # Environment
@@ -281,17 +291,16 @@ Machine: {uname_machine}
281
291
  Initialized in directory (also cwd): {context.bash_state.cwd}
282
292
  User home directory: {expanduser("~")}
283
293
 
284
- {repo_context}
285
-
286
294
  {alignment_context}
287
- {initial_files_context}
295
+ {repo_context}
288
296
 
289
297
  ---
290
298
 
291
299
  {memory}
292
-
293
300
  ---
294
- Use chat_id={context.bash_state.current_chat_id} for all wcgw tool calls which take that.
301
+
302
+ {initial_files_context}
303
+
295
304
  """
296
305
 
297
306
  return output, context, initial_paths_with_ranges
@@ -313,13 +322,13 @@ def reset_wcgw(
313
322
  starting_directory: str,
314
323
  mode_name: Optional[Modes],
315
324
  change_mode: ModesConfig,
316
- chat_id: str,
325
+ thread_id: str,
317
326
  ) -> str:
318
- # Load state for this chat_id before proceeding with mode/directory changes
319
- if chat_id != context.bash_state.current_chat_id:
320
- # Try to load state from the chat ID
321
- if not context.bash_state.load_state_from_chat_id(chat_id):
322
- return f"Error: No saved bash state found for chat ID {chat_id}. Please re-initialize to get a new id or use correct id."
327
+ # Load state for this thread_id before proceeding with mode/directory changes
328
+ if thread_id != context.bash_state.current_thread_id:
329
+ # Try to load state from the thread_id
330
+ if not context.bash_state.load_state_from_thread_id(thread_id):
331
+ 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
332
  if mode_name:
324
333
  # update modes if they're relative
325
334
  if isinstance(change_mode, CodeWriterMode):
@@ -332,7 +341,7 @@ def reset_wcgw(
332
341
  change_mode
333
342
  )
334
343
 
335
- # Reset shell with new mode, using the provided chat ID
344
+ # Reset shell with new mode, using the provided thread_id
336
345
  context.bash_state.load_state(
337
346
  bash_command_mode,
338
347
  file_edit_mode,
@@ -341,7 +350,7 @@ def reset_wcgw(
341
350
  dict(context.bash_state.whitelist_for_overwrite),
342
351
  starting_directory,
343
352
  starting_directory,
344
- chat_id,
353
+ thread_id,
345
354
  )
346
355
  mode_prompt = get_mode_prompt(context)
347
356
  return (
@@ -357,7 +366,7 @@ def reset_wcgw(
357
366
  write_if_empty_mode = context.bash_state.write_if_empty_mode
358
367
  mode = context.bash_state.mode
359
368
 
360
- # Reload state with new directory, using the provided chat ID
369
+ # Reload state with new directory, using the provided thread_id
361
370
  context.bash_state.load_state(
362
371
  bash_command_mode,
363
372
  file_edit_mode,
@@ -366,7 +375,7 @@ def reset_wcgw(
366
375
  dict(context.bash_state.whitelist_for_overwrite),
367
376
  starting_directory,
368
377
  starting_directory,
369
- chat_id,
378
+ thread_id,
370
379
  )
371
380
  return "Reset successful" + get_status(context.bash_state)
372
381
 
@@ -508,7 +517,7 @@ def write_file(
508
517
  error_on_exist_ = (
509
518
  error_on_exist and path_ not in context.bash_state.whitelist_for_overwrite
510
519
  )
511
-
520
+ curr_hash = ""
512
521
  if error_on_exist and path_ in context.bash_state.whitelist_for_overwrite:
513
522
  # Ensure hash has not changed
514
523
  if os.path.exists(path_):
@@ -819,12 +828,14 @@ def file_writing(
819
828
  If percentage_changed > 50%, treat content as direct file content.
820
829
  Otherwise, treat content as search/replace blocks.
821
830
  """
822
- # Check if the chat ID matches current
823
- if file_writing_args.chat_id != context.bash_state.current_chat_id:
824
- # Try to load state from the chat ID
825
- if not context.bash_state.load_state_from_chat_id(file_writing_args.chat_id):
831
+ # Check if the thread_id matches current
832
+ if file_writing_args.thread_id != context.bash_state.current_thread_id:
833
+ # Try to load state from the thread_id
834
+ if not context.bash_state.load_state_from_thread_id(
835
+ file_writing_args.thread_id
836
+ ):
826
837
  return (
827
- f"Error: No saved bash state found for chat ID {file_writing_args.chat_id}. Please re-initialize to get a new id or use correct id.",
838
+ 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
839
  {},
829
840
  )
830
841
 
@@ -837,14 +848,14 @@ def file_writing(
837
848
  )
838
849
 
839
850
  # If file doesn't exist, always use direct file_content mode
840
- content = file_writing_args.file_content_or_search_replace_blocks
851
+ content = file_writing_args.text_or_search_replace_blocks
841
852
 
842
853
  if not _is_edit(content, file_writing_args.percentage_to_change):
843
854
  # Use direct content mode (same as WriteIfEmpty)
844
855
  result, paths = write_file(
845
856
  WriteIfEmpty(
846
857
  file_path=path_,
847
- file_content=file_writing_args.file_content_or_search_replace_blocks,
858
+ file_content=file_writing_args.text_or_search_replace_blocks,
848
859
  ),
849
860
  True,
850
861
  coding_max_tokens,
@@ -857,7 +868,7 @@ def file_writing(
857
868
  result, paths = do_diff_edit(
858
869
  FileEdit(
859
870
  file_path=path_,
860
- file_edit_using_search_replace_blocks=file_writing_args.file_content_or_search_replace_blocks,
871
+ file_edit_using_search_replace_blocks=file_writing_args.text_or_search_replace_blocks,
861
872
  ),
862
873
  coding_max_tokens,
863
874
  noncoding_max_tokens,
@@ -1012,7 +1023,7 @@ def get_tool_output(
1012
1023
  )
1013
1024
  workspace_path = workspace_path if os.path.exists(workspace_path) else ""
1014
1025
 
1015
- # For these specific operations, chat_id is required
1026
+ # For these specific operations, thread_id is required
1016
1027
  output = (
1017
1028
  reset_wcgw(
1018
1029
  context,
@@ -1021,7 +1032,7 @@ def get_tool_output(
1021
1032
  if is_mode_change(arg.mode, context.bash_state)
1022
1033
  else None,
1023
1034
  arg.mode,
1024
- arg.chat_id,
1035
+ arg.thread_id,
1025
1036
  ),
1026
1037
  0.0,
1027
1038
  )
@@ -1035,7 +1046,7 @@ def get_tool_output(
1035
1046
  coding_max_tokens,
1036
1047
  noncoding_max_tokens,
1037
1048
  arg.mode,
1038
- arg.chat_id,
1049
+ arg.thread_id,
1039
1050
  )
1040
1051
  output = output_, 0.0
1041
1052
  # Since init_paths is already a dictionary mapping file paths to line ranges,
@@ -1315,7 +1326,7 @@ if __name__ == "__main__":
1315
1326
  task_id_to_resume="",
1316
1327
  mode_name="wcgw",
1317
1328
  code_writer_config=None,
1318
- chat_id="",
1329
+ thread_id="",
1319
1330
  ),
1320
1331
  default_enc,
1321
1332
  0,
@@ -1329,7 +1340,7 @@ if __name__ == "__main__":
1329
1340
  Context(BASH_STATE, BASH_STATE.console),
1330
1341
  BashCommand(
1331
1342
  action_json=Command(command="pwd"),
1332
- chat_id=BASH_STATE.current_chat_id,
1343
+ thread_id=BASH_STATE.current_thread_id,
1333
1344
  ),
1334
1345
  default_enc,
1335
1346
  0,
@@ -1359,9 +1370,9 @@ if __name__ == "__main__":
1359
1370
  Context(BASH_STATE, BASH_STATE.console),
1360
1371
  FileWriteOrEdit(
1361
1372
  file_path="/Users/arusia/repos/wcgw/src/wcgw/client/tools.py",
1362
- file_content_or_search_replace_blocks="""test""",
1373
+ text_or_search_replace_blocks="""test""",
1363
1374
  percentage_to_change=100,
1364
- chat_id=BASH_STATE.current_chat_id,
1375
+ thread_id=BASH_STATE.current_thread_id,
1365
1376
  ),
1366
1377
  default_enc,
1367
1378
  0,
wcgw/types_.py CHANGED
@@ -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
- chat_id: str = Field(
57
- description="Use the chat id created in first_call, leave it as empty string if first_call"
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
 
@@ -63,6 +63,10 @@ class Initialize(BaseModel):
63
63
  assert self.code_writer_config is not None, (
64
64
  "code_writer_config can't be null when the mode is code_writer"
65
65
  )
66
+ if self.type != "first_call" and not self.thread_id:
67
+ raise ValueError(
68
+ "Thread id should be provided if type != 'first_call', including when resetting"
69
+ )
66
70
  return super().model_post_init(__context)
67
71
 
68
72
  @property
@@ -105,7 +109,7 @@ class SendAscii(BaseModel):
105
109
  class BashCommand(BaseModel):
106
110
  action_json: Command | StatusCheck | SendText | SendSpecials | SendAscii
107
111
  wait_for_seconds: Optional[float] = None
108
- chat_id: str
112
+ thread_id: str
109
113
 
110
114
 
111
115
  class ReadImage(BaseModel):
@@ -215,10 +219,15 @@ class FileEdit(BaseModel):
215
219
 
216
220
 
217
221
  class FileWriteOrEdit(BaseModel):
218
- file_path: str
219
- percentage_to_change: int # 0.0 to 100.0
220
- file_content_or_search_replace_blocks: str
221
- chat_id: str
222
+ # Naming should be in sorted order otherwise it gets changed in LLM backend.
223
+ file_path: str = Field(description="#1: absolute file path")
224
+ percentage_to_change: int = Field(
225
+ description="#2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines."
226
+ )
227
+ text_or_search_replace_blocks: str = Field(
228
+ description="#3: content/edit blocks. Must be after #2 in the tool xml"
229
+ )
230
+ thread_id: str = Field(description="#4: thread_id")
222
231
 
223
232
 
224
233
  class ContextSave(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 5.1.2
3
+ Version: 5.2.0
4
4
  Summary: Shell and coding agent for Claude and other mcp clients
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -19,7 +19,7 @@ Requires-Dist: pyte>=0.8.2
19
19
  Requires-Dist: python-dotenv>=1.0.1
20
20
  Requires-Dist: rich>=13.8.1
21
21
  Requires-Dist: semantic-version>=2.10.0
22
- Requires-Dist: syntax-checker>=0.3.0
22
+ Requires-Dist: syntax-checker==0.4.0b4
23
23
  Requires-Dist: tokenizers>=0.21.0
24
24
  Requires-Dist: toml>=0.10.2
25
25
  Requires-Dist: tree-sitter-bash>=0.23.3
@@ -152,6 +152,31 @@ Then add or update the claude config file `%APPDATA%\Claude\claude_desktop_confi
152
152
  }
153
153
  }
154
154
  ```
155
+ When you encounter an error, execute the command wsl uv --python 3.12 wcgw@latest in command prompt. If you get the `error /bin/bash: line 1: uv: command not found`, it means uv was not installed globally and you need to point to the correct path of uv.
156
+ 1. Find where uv is installed:
157
+ ```bash
158
+ whereis uv
159
+ ```
160
+ Example output:
161
+ ```uv: /home/mywsl/.local/bin/uv```
162
+
163
+ 2. Test the full path works:
164
+ ```
165
+ wsl /home/mywsl/.local/bin/uv tool run --python 3.12 wcgw@latest
166
+ ```
167
+
168
+ 3. Update the config with the full path:
169
+ ```
170
+ {
171
+ "mcpServers": {
172
+ "wcgw": {
173
+ "command": "wsl.exe",
174
+ "args": ["/home/mywsl/.local/bin/uv", "tool", "run", "--python", "3.12", "wcgw@latest"]
175
+ }
176
+ }
177
+ }
178
+ ```
179
+ Replace `/home/mywsl/.local/bin/uv` with your actual uv path from step 1.
155
180
 
156
181
  ### Usage
157
182
 
@@ -1,20 +1,20 @@
1
1
  wcgw/__init__.py,sha256=JgAY25VsA208v8E7QTIU0E50nsk-TCJ4FWTEHmnssYU,127
2
2
  wcgw/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- wcgw/types_.py,sha256=y60Lv_uUA1_sGIfADLUKy7rFPTax8jxor5GGCDKBfZ0,7533
3
+ wcgw/types_.py,sha256=nTj0Lr_ytBDHoeQ94ebO0mzuhsluBG6GmtT9eszIjuI,8141
4
4
  wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  wcgw/client/common.py,sha256=OCH7Tx64jojz3M3iONUrGMadE07W21DiZs5sOxWX1Qc,1456
6
6
  wcgw/client/diff-instructions.txt,sha256=eKRFA86yXWIGwNxIDaegTgTzIrFIBDWWiN1yP8Hf3i4,1685
7
7
  wcgw/client/memory.py,sha256=U2Nw2si3Zg7n_RhNAuaYcmrrDtZ_Mooi-kfAOKflT-I,3079
8
8
  wcgw/client/modes.py,sha256=roH6SPBokJMr5IzAlccdI-vJyvyS5vqSMMyth7TE86A,10315
9
- wcgw/client/tool_prompts.py,sha256=Qgj8aqEFeKilF9otDu6ZQkV0Ig4l6JaeM99uWu4ecDQ,4802
10
- wcgw/client/tools.py,sha256=y1Li2Wtnh5KkDoxCsQne-eCksjMlclBcgftzPVc3Vfw,48375
11
- wcgw/client/bash_state/bash_state.py,sha256=GXBh9zcCqSgUCKn9ZtVLosfqOkJRwrzO-qzyETNHTeE,41744
9
+ wcgw/client/tool_prompts.py,sha256=0hfYmCzCo6kh_IC8RGJRfQ6hkB333GZvs_g3o_cvc2w,4662
10
+ wcgw/client/tools.py,sha256=Xo1lOLfzuDNZljESpkSOXlPUKG_4WJGAFaZ7K7ilV8s,48788
11
+ wcgw/client/bash_state/bash_state.py,sha256=b-Zo6pO3psfxu9TA0uw1ZZAa6bLmDPxbl7277fZfmrM,41862
12
12
  wcgw/client/bash_state/parser/__init__.py,sha256=AnlNSmoQTSoqqlLOLX4P1uXfzc5VGeCGJsGgtisq2zE,207
13
13
  wcgw/client/bash_state/parser/bash_statement_parser.py,sha256=9a8vPO1r3_tXmaAcubTQ5UY-NseWlalgm8LZA17LXuY,6058
14
14
  wcgw/client/encoder/__init__.py,sha256=Y-8f43I6gMssUCWpX5rLYiAFv3D-JPRs4uNEejPlke8,1514
15
15
  wcgw/client/file_ops/diff_edit.py,sha256=AwLq6-pY7czv1y-JA5O2Q4rgbvn82YmSL9jD8XB3Vo4,19019
16
16
  wcgw/client/file_ops/extensions.py,sha256=CmfD7ON6SY24Prh2tRZdV9KbhuOrWqqk8qL1VtshzB8,3608
17
- wcgw/client/file_ops/search_replace.py,sha256=bB1S-xkDdOX4h7UHHxfCHaQiS9KyYkIdU6UGvIrgwQM,6820
17
+ wcgw/client/file_ops/search_replace.py,sha256=5LFg-_U_ijnNrkYei4SWCPGKPGgDzJs49EDsIBzLmuY,6822
18
18
  wcgw/client/mcp_server/Readme.md,sha256=2Z88jj1mf9daYGW1CWaldcJ0moy8owDumhR2glBY3A8,109
19
19
  wcgw/client/mcp_server/__init__.py,sha256=mm7xhBIPwJpRT3u-Qsj4cKVMpVyucJoKRlbMP_gRRB0,343
20
20
  wcgw/client/mcp_server/server.py,sha256=jjwrmZZ8X0tXD0rsPZ9fKjEpdXpXCfdhEsN3Ho_tC8I,4989
@@ -30,8 +30,8 @@ wcgw_cli/anthropic_client.py,sha256=8bjDY59-aioyTJgpB-NBHZNhZaq6rqcTJcOf81kzCyA,
30
30
  wcgw_cli/cli.py,sha256=-7FBe_lahKyUOhf65iurTA1M1gXXXAiT0OVKQVcZKKo,948
31
31
  wcgw_cli/openai_client.py,sha256=GOqoSFazTV-cFjpdZGPM0DIwec8Up2TEcKUbsN40AGY,15990
32
32
  wcgw_cli/openai_utils.py,sha256=xGOb3W5ALrIozV7oszfGYztpj0FnXdD7jAxm5lEIVKY,2439
33
- wcgw-5.1.2.dist-info/METADATA,sha256=5SrTq2BhXtZcufNDlO9N8eqjqMkxRxQafzgj1QWGMpM,14755
34
- wcgw-5.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- wcgw-5.1.2.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
36
- wcgw-5.1.2.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
37
- wcgw-5.1.2.dist-info/RECORD,,
33
+ wcgw-5.2.0.dist-info/METADATA,sha256=EW_fVplOiHFH1b8033vhslBodxghzY4Qw-03iugwuDg,15512
34
+ wcgw-5.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
+ wcgw-5.2.0.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
36
+ wcgw-5.2.0.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
37
+ wcgw-5.2.0.dist-info/RECORD,,
File without changes