wcgw 2.5.0__py3-none-any.whl → 2.6.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.

@@ -1,34 +1,33 @@
1
- import asyncio
2
1
  import importlib
3
2
  import json
4
3
  import os
5
- import sys
6
- import traceback
7
4
  from typing import Any
8
5
 
9
- from mcp_wcgw.server.models import InitializationOptions
6
+ from pydantic import AnyUrl, ValidationError
7
+
8
+ import mcp_wcgw.server.stdio
10
9
  import mcp_wcgw.types as types
11
- from mcp_wcgw.types import Tool as ToolParam
12
10
  from mcp_wcgw.server import NotificationOptions, Server
13
- from pydantic import AnyUrl, BaseModel, ValidationError
14
- import mcp_wcgw.server.stdio
15
- from .. import tools
16
- from ..tools import DoneFlag, get_tool_output, which_tool_name, default_enc
11
+ from mcp_wcgw.server.models import InitializationOptions
12
+ from mcp_wcgw.types import Tool as ToolParam
13
+
17
14
  from ...types_ import (
18
15
  BashCommand,
19
16
  BashInteraction,
20
- WriteIfEmpty,
21
17
  FileEdit,
18
+ GetScreenInfo,
19
+ Initialize,
22
20
  Keyboard,
23
21
  Mouse,
24
22
  ReadFiles,
25
23
  ReadImage,
26
24
  ResetShell,
27
- Initialize,
28
25
  ScreenShot,
29
- GetScreenInfo,
26
+ WriteIfEmpty,
30
27
  )
28
+ from .. import tools
31
29
  from ..computer_use import SLEEP_TIME_MAX_S
30
+ from ..tools import DoneFlag, default_enc, get_tool_output, which_tool_name
32
31
 
33
32
  COMPUTER_USE_ON_DOCKER_ENABLED = False
34
33
 
@@ -77,6 +76,12 @@ async def handle_list_tools() -> list[types.Tool]:
77
76
  name="Initialize",
78
77
  description="""
79
78
  - Always call this at the start of the conversation before using any of the shell tools from wcgw.
79
+ - This will reset the shell.
80
+ - Use `any_workspace_path` to initialize the shell in the appropriate project directory.
81
+ - If the user has mentioned a workspace or project root, use it to set `any_workspace_path`.
82
+ - If the user has mentioned a folder or file with unclear project root, use the file or folder as `any_workspace_path`.
83
+ - If user has mentioned any files use `initial_files_to_read` to read, use absolute paths only.
84
+ - If `any_workspace_path` is provided, a tree structure of the workspace will be shown.
80
85
  """,
81
86
  ),
82
87
  ToolParam(
@@ -236,24 +241,24 @@ async def handle_call_tool(
236
241
  if isinstance(output_or_done, str):
237
242
  if issubclass(tool_type, Initialize):
238
243
  output_or_done += """
239
-
240
- You're an expert software engineer with shell and code knowledge.
241
-
242
- Instructions:
244
+ ---
245
+ You're an expert software engineer with shell and code knowledge.
246
+
247
+ Instructions:
248
+
249
+ - You should use the provided bash execution, reading and writing file tools to complete objective.
250
+ - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
251
+ - Always read relevant files before editing.
252
+ - Do not provide code snippets unless asked by the user, instead directly add/edit the code.
253
+ - Do not install new tools/packages before ensuring no such tools/package or an alternative already exists.
254
+ - Do not use artifacts if you have access to the repository and not asked by the user to provide artifacts/snippets. Directly create/update using shell tools.
255
+ - Do not use Ctrl-c or Ctrl-z or interrupt commands without asking the user, because often the program don't show any update but they still are running.
256
+ - Do not use echo to write multi-line files, always use FileEdit tool to update a code.
243
257
 
244
- - You should use the provided bash execution, reading and writing file tools to complete objective.
245
- - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
246
- - Always read relevant files before editing.
247
- - Do not provide code snippets unless asked by the user, instead directly add/edit the code.
248
- - Do not install new tools/packages before ensuring no such tools/package or an alternative already exists.
249
- - Do not use artifacts if you have access to the repository and not asked by the user to provide artifacts/snippets. Directly create/update using shell tools.
250
- - Do not use Ctrl-c or Ctrl-z or interrupt commands without asking the user, because often the program don't show any update but they still are running.
251
- - Do not use echo to write multi-line files, always use FileEdit tool to update a code.
252
-
253
- Additional instructions:
254
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost, or to get absolute cwd.
255
-
256
- Always write production ready, syntactically correct code.
258
+ Additional instructions:
259
+ Always run `pwd` if you get any file or directory not found error to make sure you're not lost, or to get absolute cwd.
260
+
261
+ Always write production ready, syntactically correct code.
257
262
  """
258
263
 
259
264
  content.append(types.TextContent(type="text", text=output_or_done))
@@ -1,58 +1,46 @@
1
1
  import base64
2
2
  import json
3
3
  import mimetypes
4
- from pathlib import Path
5
- import sys
4
+ import os
5
+ import subprocess
6
+ import tempfile
6
7
  import traceback
7
- from typing import Callable, DefaultDict, Optional, cast
8
+ import uuid
9
+ from pathlib import Path
10
+ from typing import DefaultDict, Optional, cast
11
+
8
12
  import openai
13
+ import petname # type: ignore[import-untyped]
14
+ import rich
15
+ import tokenizers # type: ignore[import-untyped]
16
+ from dotenv import load_dotenv
9
17
  from openai import OpenAI
10
18
  from openai.types.chat import (
19
+ ChatCompletionContentPartParam,
11
20
  ChatCompletionMessageParam,
12
- ChatCompletionAssistantMessageParam,
13
21
  ChatCompletionUserMessageParam,
14
- ChatCompletionContentPartParam,
15
- ChatCompletionMessage,
16
- ParsedChatCompletionMessage,
17
22
  )
18
- import rich
19
- import petname # type: ignore[import-untyped]
20
- import tokenizers # type: ignore[import-untyped]
23
+ from pydantic import BaseModel
21
24
  from typer import Typer
22
- import uuid
23
25
 
24
26
  from ..types_ import (
25
27
  BashCommand,
26
28
  BashInteraction,
27
- WriteIfEmpty,
28
29
  FileEdit,
29
- ReadImage,
30
30
  ReadFiles,
31
+ ReadImage,
31
32
  ResetShell,
33
+ WriteIfEmpty,
32
34
  )
33
-
34
- from .common import Models, discard_input
35
- from .common import CostData, History
35
+ from .common import CostData, History, Models, discard_input
36
36
  from .openai_utils import get_input_cost, get_output_cost
37
- from .tools import ImageData
38
-
39
37
  from .tools import (
40
38
  DoneFlag,
39
+ ImageData,
41
40
  get_tool_output,
42
41
  which_tool,
43
42
  )
44
43
 
45
- from urllib import parse
46
- import subprocess
47
- import os
48
- import tempfile
49
-
50
- import toml
51
- from pydantic import BaseModel
52
-
53
-
54
- from dotenv import load_dotenv
55
-
56
44
 
57
45
  class Config(BaseModel):
58
46
  model: Models
@@ -0,0 +1,127 @@
1
+ import io
2
+ from pathlib import Path
3
+ from typing import List, Set
4
+
5
+
6
+ class DirectoryTree:
7
+ def __init__(self, root: Path, max_files: int = 10):
8
+ """
9
+ Initialize the DirectoryTree with a root path and maximum number of files to display
10
+
11
+ Args:
12
+ root_path: The root directory path to start from
13
+ max_files: Maximum number of files to display in unexpanded directories
14
+ """
15
+ self.root = root
16
+ self.max_files = max_files
17
+ self.expanded_files: Set[Path] = set()
18
+ self.expanded_dirs = set[Path]()
19
+
20
+ if not self.root.exists():
21
+ raise ValueError(f"Root path {root} does not exist")
22
+
23
+ if not self.root.is_dir():
24
+ raise ValueError(f"Root path {root} is not a directory")
25
+
26
+ def expand(self, rel_path: str) -> None:
27
+ """
28
+ Expand a specific file in the tree
29
+
30
+ Args:
31
+ rel_path: Relative path from root to the file to expand
32
+ """
33
+ abs_path = self.root / rel_path
34
+
35
+ if not abs_path.exists():
36
+ raise ValueError(f"Path {rel_path} does not exist")
37
+
38
+ if not abs_path.is_file():
39
+ raise ValueError(f"Path {rel_path} is not a file")
40
+
41
+ if not str(abs_path).startswith(str(self.root)):
42
+ raise ValueError(f"Path {rel_path} is outside root directory")
43
+
44
+ self.expanded_files.add(abs_path)
45
+
46
+ # Add all parent directories to expanded dirs
47
+ current = abs_path.parent
48
+ while str(current) >= str(self.root):
49
+ if current not in self.expanded_dirs:
50
+ self.expanded_dirs.add(current)
51
+ if current == current.parent:
52
+ break
53
+ current = current.parent
54
+
55
+ def _list_directory(self, dir_path: Path) -> List[Path]:
56
+ """List contents of a directory, sorted with directories first"""
57
+ contents = list(dir_path.iterdir())
58
+ return sorted(contents, key=lambda x: (not x.is_dir(), x.name.lower()))
59
+
60
+ def _count_hidden_items(
61
+ self, dir_path: Path, shown_items: List[Path]
62
+ ) -> tuple[int, int]:
63
+ """Count hidden files and directories in a directory"""
64
+ all_items = set(self._list_directory(dir_path))
65
+ shown_items_set = set(shown_items)
66
+ hidden_items = all_items - shown_items_set
67
+
68
+ hidden_files = sum(1 for p in hidden_items if p.is_file())
69
+ hidden_dirs = sum(1 for p in hidden_items if p.is_dir())
70
+
71
+ return hidden_files, hidden_dirs
72
+
73
+ def display(self) -> str:
74
+ """Display the directory tree with expanded state"""
75
+ writer = io.StringIO()
76
+
77
+ def _display_recursive(
78
+ current_path: Path, indent: int = 0, depth: int = 0
79
+ ) -> None:
80
+ # Print current directory name
81
+ if current_path == self.root:
82
+ writer.write(f"{current_path}\n")
83
+ else:
84
+ writer.write(f"{' ' * indent}{current_path.name}\n")
85
+
86
+ # Don't recurse beyond depth 1 unless path contains expanded files
87
+ if depth > 0 and current_path not in self.expanded_dirs:
88
+ return
89
+
90
+ # Get directory contents
91
+ contents = self._list_directory(current_path)
92
+ shown_items = []
93
+
94
+ for item in contents:
95
+ # Show items only if:
96
+ # 1. They are expanded files
97
+ # 2. They are parents of expanded items
98
+ should_show = item in self.expanded_files or item in self.expanded_dirs
99
+
100
+ if should_show:
101
+ shown_items.append(item)
102
+ if item.is_dir():
103
+ _display_recursive(item, indent + 2, depth + 1)
104
+ else:
105
+ writer.write(f"{' ' * (indent + 2)}{item.name}\n")
106
+
107
+ # Show hidden items count if any items were hidden
108
+ hidden_files, hidden_dirs = self._count_hidden_items(
109
+ current_path, shown_items
110
+ )
111
+ if hidden_files > 0 or hidden_dirs > 0:
112
+ hidden_msg = []
113
+ if hidden_dirs > 0:
114
+ hidden_msg.append(
115
+ f"{hidden_dirs} director{'ies' if hidden_dirs != 1 else 'y'}"
116
+ )
117
+ if hidden_files > 0:
118
+ hidden_msg.append(
119
+ f"{hidden_files} file{'s' if hidden_files != 1 else ''}"
120
+ )
121
+ writer.write(
122
+ f"{' ' * (indent + 2)}... {' and '.join(hidden_msg)} hidden\n"
123
+ )
124
+
125
+ _display_recursive(self.root, depth=0)
126
+
127
+ return writer.getvalue()
@@ -0,0 +1,58 @@
1
+ from typing import Dict, List, Tuple
2
+
3
+ import tokenizers # type: ignore[import-untyped]
4
+
5
+
6
+ class FastPathAnalyzer:
7
+ def __init__(self, model_path: str, vocab_path: str) -> None:
8
+ """Initialize with vocabulary."""
9
+ # Load vocabulary and probabilities
10
+ self.vocab_probs: Dict[str, float] = {}
11
+ with open(vocab_path, "r") as f:
12
+ for line in f:
13
+ parts = line.strip().split()
14
+ if len(parts) == 2:
15
+ token, prob = parts
16
+ try:
17
+ self.vocab_probs[token] = float(prob)
18
+ except ValueError:
19
+ continue
20
+
21
+ self.encoder = tokenizers.Tokenizer.from_file(model_path)
22
+
23
+ def tokenize_batch(self, texts: List[str]) -> List[List[str]]:
24
+ """Tokenize multiple texts at once."""
25
+ encodings = self.encoder.encode_batch(texts)
26
+ return [encoding.tokens for encoding in encodings]
27
+
28
+ def detokenize(self, tokens: List[str]) -> str:
29
+ """Convert tokens back to text, handling special tokens."""
30
+ return self.encoder.decode(tokens) # type: ignore[no-any-return]
31
+
32
+ def calculate_path_probabilities_batch(
33
+ self, paths: List[str]
34
+ ) -> List[Tuple[float, List[str], List[str]]]:
35
+ """Calculate log probability for multiple paths at once."""
36
+ # Batch tokenize all paths
37
+ all_tokens = self.tokenize_batch(paths)
38
+
39
+ results = []
40
+ for tokens in all_tokens:
41
+ # Calculate sum of log probabilities for each path
42
+ log_prob_sum = 0.0
43
+ unknown_tokens = []
44
+ for token in tokens:
45
+ if token in self.vocab_probs:
46
+ log_prob_sum += self.vocab_probs[token]
47
+ else:
48
+ unknown_tokens.append(token)
49
+
50
+ results.append((log_prob_sum, tokens, unknown_tokens))
51
+
52
+ return results
53
+
54
+ def calculate_path_probability(
55
+ self, path: str
56
+ ) -> Tuple[float, List[str], List[str]]:
57
+ """Calculate log probability for a single path."""
58
+ return self.calculate_path_probabilities_batch([path])[0]