wcgw 2.8.10__py3-none-any.whl → 3.0.1__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.
- wcgw/__init__.py +1 -1
- wcgw/client/bash_state/bash_state.py +788 -0
- wcgw/client/encoder/__init__.py +47 -0
- wcgw/client/file_ops/diff_edit.py +52 -1
- wcgw/client/mcp_server/Readme.md +2 -88
- wcgw/client/mcp_server/__init__.py +2 -2
- wcgw/client/mcp_server/server.py +54 -214
- wcgw/client/modes.py +16 -21
- wcgw/client/repo_ops/display_tree.py +1 -12
- wcgw/client/tool_prompts.py +99 -0
- wcgw/client/tools.py +286 -1086
- wcgw/py.typed +0 -0
- wcgw/relay/client.py +95 -0
- wcgw/relay/serve.py +4 -69
- wcgw/types_.py +57 -63
- {wcgw-2.8.10.dist-info → wcgw-3.0.1.dist-info}/METADATA +4 -3
- {wcgw-2.8.10.dist-info → wcgw-3.0.1.dist-info}/RECORD +23 -20
- wcgw_cli/anthropic_client.py +269 -366
- wcgw_cli/cli.py +0 -2
- wcgw_cli/openai_client.py +224 -280
- wcgw/client/computer_use.py +0 -435
- wcgw/client/sys_utils.py +0 -41
- {wcgw-2.8.10.dist-info → wcgw-3.0.1.dist-info}/WHEEL +0 -0
- {wcgw-2.8.10.dist-info → wcgw-3.0.1.dist-info}/entry_points.txt +0 -0
- {wcgw-2.8.10.dist-info → wcgw-3.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from typing import Callable, Protocol, TypeVar, cast
|
|
3
|
+
|
|
4
|
+
import tokenizers # type: ignore[import-untyped]
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EncoderDecoder(Protocol[T]):
|
|
10
|
+
def encoder(self, text: str) -> list[T]: ...
|
|
11
|
+
|
|
12
|
+
def decoder(self, tokens: list[T]) -> str: ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LazyEncoder:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self._tokenizer: tokenizers.Tokenizer | None = None
|
|
18
|
+
self._init_lock = threading.Lock()
|
|
19
|
+
self._init_thread = threading.Thread(target=self._initialize, daemon=True)
|
|
20
|
+
self._init_thread.start()
|
|
21
|
+
|
|
22
|
+
def _initialize(self) -> None:
|
|
23
|
+
with self._init_lock:
|
|
24
|
+
if self._tokenizer is None:
|
|
25
|
+
self._tokenizer = tokenizers.Tokenizer.from_pretrained(
|
|
26
|
+
"Xenova/claude-tokenizer"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def _ensure_initialized(self) -> None:
|
|
30
|
+
if self._tokenizer is None:
|
|
31
|
+
with self._init_lock:
|
|
32
|
+
if self._tokenizer is None:
|
|
33
|
+
self._init_thread.join()
|
|
34
|
+
|
|
35
|
+
def encoder(self, text: str) -> list[int]:
|
|
36
|
+
self._ensure_initialized()
|
|
37
|
+
assert self._tokenizer is not None, "Couldn't initialize tokenizer"
|
|
38
|
+
return cast(list[int], self._tokenizer.encode(text).ids)
|
|
39
|
+
|
|
40
|
+
def decoder(self, tokens: list[int]) -> str:
|
|
41
|
+
self._ensure_initialized()
|
|
42
|
+
assert self._tokenizer is not None, "Couldn't initialize tokenizer"
|
|
43
|
+
return cast(str, self._tokenizer.decode(tokens))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_default_encoder() -> EncoderDecoder[int]:
|
|
47
|
+
return LazyEncoder()
|
|
@@ -102,6 +102,8 @@ def line_process_max_space_tolerance(line: str) -> str:
|
|
|
102
102
|
return re.sub(r"\s", "", line)
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
REMOVE_INDENTATION = "Warning: matching after removing all spaces in lines."
|
|
106
|
+
|
|
105
107
|
DEFAULT_TOLERANCES = [
|
|
106
108
|
Tolerance(
|
|
107
109
|
line_process=str.rstrip,
|
|
@@ -119,11 +121,50 @@ DEFAULT_TOLERANCES = [
|
|
|
119
121
|
line_process=line_process_max_space_tolerance,
|
|
120
122
|
severity_cat="WARNING",
|
|
121
123
|
score_multiplier=50,
|
|
122
|
-
error_name=
|
|
124
|
+
error_name=REMOVE_INDENTATION,
|
|
123
125
|
),
|
|
124
126
|
]
|
|
125
127
|
|
|
126
128
|
|
|
129
|
+
def fix_indentation(
|
|
130
|
+
matched_lines: list[str], searched_lines: list[str], replaced_lines: list[str]
|
|
131
|
+
) -> list[str]:
|
|
132
|
+
if not matched_lines or not searched_lines or not replaced_lines:
|
|
133
|
+
return replaced_lines
|
|
134
|
+
|
|
135
|
+
def get_indentation(line: str) -> str:
|
|
136
|
+
match = re.match(r"^(\s*)", line)
|
|
137
|
+
assert match
|
|
138
|
+
return match.group(0)
|
|
139
|
+
|
|
140
|
+
matched_indents = [get_indentation(line) for line in matched_lines if line.strip()]
|
|
141
|
+
searched_indents = [
|
|
142
|
+
get_indentation(line) for line in searched_lines if line.strip()
|
|
143
|
+
]
|
|
144
|
+
if len(matched_indents) != len(searched_indents):
|
|
145
|
+
return replaced_lines
|
|
146
|
+
diffs: list[int] = [
|
|
147
|
+
len(searched) - len(matched)
|
|
148
|
+
for matched, searched in zip(matched_indents, searched_indents)
|
|
149
|
+
]
|
|
150
|
+
if not all(diff == diffs[0] for diff in diffs):
|
|
151
|
+
return replaced_lines
|
|
152
|
+
if diffs[0] == 0:
|
|
153
|
+
return replaced_lines
|
|
154
|
+
|
|
155
|
+
# At this point we have same number of non-empty lines and the same indentation difference
|
|
156
|
+
# We can now adjust the indentation of the replaced lines
|
|
157
|
+
def adjust_indentation(line: str, diff: int) -> str:
|
|
158
|
+
if diff < 0:
|
|
159
|
+
return matched_indents[0][:-diff] + line
|
|
160
|
+
return line[diff:]
|
|
161
|
+
|
|
162
|
+
if diffs[0] > 0:
|
|
163
|
+
if not (all(not line[: diffs[0]].strip() for line in replaced_lines)):
|
|
164
|
+
return replaced_lines
|
|
165
|
+
return [adjust_indentation(line, diffs[0]) for line in replaced_lines]
|
|
166
|
+
|
|
167
|
+
|
|
127
168
|
def remove_leading_trailing_empty_lines(lines: list[str]) -> list[str]:
|
|
128
169
|
start = 0
|
|
129
170
|
end = len(lines) - 1
|
|
@@ -247,6 +288,16 @@ class FileEditInput:
|
|
|
247
288
|
]
|
|
248
289
|
|
|
249
290
|
for match, tolerances in matches_with_tolerances:
|
|
291
|
+
if any(
|
|
292
|
+
tolerance.error_name == REMOVE_INDENTATION
|
|
293
|
+
for tolerance in tolerances
|
|
294
|
+
):
|
|
295
|
+
replace_by = fix_indentation(
|
|
296
|
+
self.file_lines[match.start : match.stop],
|
|
297
|
+
first_block[0],
|
|
298
|
+
replace_by,
|
|
299
|
+
)
|
|
300
|
+
|
|
250
301
|
file_edit_input = FileEditInput(
|
|
251
302
|
self.file_lines,
|
|
252
303
|
match.stop,
|
wcgw/client/mcp_server/Readme.md
CHANGED
|
@@ -1,89 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# The doc has moved to main Readme.md
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
It also has a computer use feature to connect to linux running on docker. Claude can fully control it including mouse and keyboard.
|
|
6
|
-
|
|
7
|
-
## Setup
|
|
8
|
-
|
|
9
|
-
Update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
{
|
|
13
|
-
"mcpServers": {
|
|
14
|
-
"wcgw": {
|
|
15
|
-
"command": "uv",
|
|
16
|
-
"args": [
|
|
17
|
-
"tool",
|
|
18
|
-
"run",
|
|
19
|
-
"--from",
|
|
20
|
-
"wcgw@latest",
|
|
21
|
-
"--python",
|
|
22
|
-
"3.12",
|
|
23
|
-
"wcgw_mcp"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Then restart claude app.
|
|
31
|
-
|
|
32
|
-
### [Optional] Computer use support using desktop on docker
|
|
33
|
-
|
|
34
|
-
Computer use is disabled by default. Add `--computer-use` to enable it. This will add necessary tools to Claude including ScreenShot, Mouse and Keyboard control.
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"mcpServers": {
|
|
39
|
-
"wcgw": {
|
|
40
|
-
"command": "uv",
|
|
41
|
-
"args": [
|
|
42
|
-
"tool",
|
|
43
|
-
"run",
|
|
44
|
-
"--from",
|
|
45
|
-
"wcgw@latest",
|
|
46
|
-
"--python",
|
|
47
|
-
"3.12",
|
|
48
|
-
"wcgw_mcp",
|
|
49
|
-
"--computer-use"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Claude will be able to connect to any docker container with linux environment. Native system control isn't supported outside docker.
|
|
57
|
-
|
|
58
|
-
You'll need to run a docker image with desktop and optional VNC connection. Here's a demo image:
|
|
59
|
-
|
|
60
|
-
```sh
|
|
61
|
-
docker run -p 6080:6080 ghcr.io/anthropics/anthropic-quickstarts:computer-use-demo-latest
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Then ask claude desktop app to control the docker os. It'll connect to the docker container and control it.
|
|
65
|
-
|
|
66
|
-
Connect to `http://localhost:6080/vnc.html` for desktop view (VNC) of the system running in the docker.
|
|
67
|
-
|
|
68
|
-
## Usage
|
|
69
|
-
|
|
70
|
-
Wait for a few seconds. You should be able to see this icon if everything goes right.
|
|
71
|
-
|
|
72
|
-

|
|
73
|
-
over here
|
|
74
|
-
|
|
75
|
-

|
|
76
|
-
|
|
77
|
-
Then ask claude to execute shell commands, read files, edit files, run your code, etc.
|
|
78
|
-
|
|
79
|
-
If you've run the docker for LLM to access, you can ask it to control the "docker os". If you don't provide the docker container id to it, it'll try to search for available docker using `docker ps` command.
|
|
80
|
-
|
|
81
|
-
## Example
|
|
82
|
-
|
|
83
|
-
### Computer use example
|
|
84
|
-
|
|
85
|
-

|
|
86
|
-
|
|
87
|
-
### Shell example
|
|
88
|
-
|
|
89
|
-

|
|
3
|
+

|
|
@@ -7,9 +7,9 @@ main = Typer()
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@main.command()
|
|
10
|
-
def app(
|
|
10
|
+
def app() -> None:
|
|
11
11
|
"""Main entry point for the package."""
|
|
12
|
-
asyncio.run(server.main(
|
|
12
|
+
asyncio.run(server.main())
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# Optionally expose other important items at package level
|
wcgw/client/mcp_server/server.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import importlib
|
|
2
|
-
import json
|
|
3
2
|
import logging
|
|
4
3
|
import os
|
|
5
4
|
from typing import Any
|
|
@@ -9,29 +8,22 @@ import mcp_wcgw.types as types
|
|
|
9
8
|
from mcp_wcgw.server import NotificationOptions, Server
|
|
10
9
|
from mcp_wcgw.server.models import InitializationOptions
|
|
11
10
|
from mcp_wcgw.types import Tool as ToolParam
|
|
12
|
-
from pydantic import AnyUrl
|
|
11
|
+
from pydantic import AnyUrl
|
|
12
|
+
|
|
13
|
+
from wcgw.client.modes import KTS
|
|
14
|
+
from wcgw.client.tool_prompts import TOOL_PROMPTS
|
|
13
15
|
|
|
14
16
|
from ...types_ import (
|
|
15
|
-
BashCommand,
|
|
16
|
-
BashInteraction,
|
|
17
|
-
ContextSave,
|
|
18
|
-
FileEdit,
|
|
19
|
-
GetScreenInfo,
|
|
20
17
|
Initialize,
|
|
21
|
-
Keyboard,
|
|
22
|
-
Mouse,
|
|
23
|
-
ReadFiles,
|
|
24
|
-
ReadImage,
|
|
25
|
-
ResetShell,
|
|
26
|
-
ScreenShot,
|
|
27
|
-
WriteIfEmpty,
|
|
28
18
|
)
|
|
29
|
-
from .. import
|
|
30
|
-
from ..
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
from ..bash_state.bash_state import CONFIG, BashState
|
|
20
|
+
from ..tools import (
|
|
21
|
+
Context,
|
|
22
|
+
default_enc,
|
|
23
|
+
get_tool_output,
|
|
24
|
+
parse_tool_by_name,
|
|
25
|
+
which_tool_name,
|
|
26
|
+
)
|
|
35
27
|
|
|
36
28
|
server = Server("wcgw")
|
|
37
29
|
|
|
@@ -64,7 +56,7 @@ PROMPTS = {
|
|
|
64
56
|
name="KnowledgeTransfer",
|
|
65
57
|
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.",
|
|
66
58
|
),
|
|
67
|
-
|
|
59
|
+
KTS,
|
|
68
60
|
)
|
|
69
61
|
}
|
|
70
62
|
|
|
@@ -78,12 +70,12 @@ async def handle_list_prompts() -> list[types.Prompt]:
|
|
|
78
70
|
async def handle_get_prompt(
|
|
79
71
|
name: str, arguments: dict[str, str] | None
|
|
80
72
|
) -> types.GetPromptResult:
|
|
73
|
+
assert BASH_STATE
|
|
81
74
|
messages = [
|
|
82
75
|
types.PromptMessage(
|
|
83
76
|
role="user",
|
|
84
77
|
content=types.TextContent(
|
|
85
|
-
type="text",
|
|
86
|
-
text=PROMPTS[name][1](),
|
|
78
|
+
type="text", text=PROMPTS[name][1][BASH_STATE.mode]
|
|
87
79
|
),
|
|
88
80
|
)
|
|
89
81
|
]
|
|
@@ -97,187 +89,37 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
|
97
89
|
Each tool specifies its arguments using JSON Schema validation.
|
|
98
90
|
"""
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
os.path.join(
|
|
102
|
-
os.path.dirname(os.path.dirname(__file__)), "diff-instructions.txt"
|
|
103
|
-
)
|
|
104
|
-
) as f:
|
|
105
|
-
diffinstructions = f.read()
|
|
106
|
-
|
|
107
|
-
tools = [
|
|
108
|
-
ToolParam(
|
|
109
|
-
inputSchema=Initialize.model_json_schema(),
|
|
110
|
-
name="Initialize",
|
|
111
|
-
description="""
|
|
112
|
-
- Always call this at the start of the conversation before using any of the shell tools from wcgw.
|
|
113
|
-
- This will reset the shell.
|
|
114
|
-
- Use `any_workspace_path` to initialize the shell in the appropriate project directory.
|
|
115
|
-
- If the user has mentioned a workspace or project root, use it to set `any_workspace_path`.
|
|
116
|
-
- If the user has mentioned a folder or file with unclear project root, use the file or folder as `any_workspace_path`.
|
|
117
|
-
- If user has mentioned any files use `initial_files_to_read` to read, use absolute paths only.
|
|
118
|
-
- If `any_workspace_path` is provided, a tree structure of the workspace will be shown.
|
|
119
|
-
- Leave `any_workspace_path` as empty if no file or folder is mentioned.
|
|
120
|
-
- By default use mode "wcgw"
|
|
121
|
-
- In "code-writer" mode, set the commands and globs which user asked to set, otherwise use 'all'.
|
|
122
|
-
- In order to change the mode later, call this tool again but be sure to not provide any other argument like task_id_to_resume unnecessarily.
|
|
123
|
-
""",
|
|
124
|
-
),
|
|
125
|
-
ToolParam(
|
|
126
|
-
inputSchema=BashCommand.model_json_schema(),
|
|
127
|
-
name="BashCommand",
|
|
128
|
-
description=f"""
|
|
129
|
-
- Execute a bash command. This is stateful (beware with subsequent calls).
|
|
130
|
-
- Do not use interactive commands like nano. Prefer writing simpler commands.
|
|
131
|
-
- Status of the command and the current working directory will always be returned at the end.
|
|
132
|
-
- Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
|
|
133
|
-
- The first or the last line might be `(...truncated)` if the output is too long.
|
|
134
|
-
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
135
|
-
- The control will return to you in {SLEEP_TIME_MAX_S} seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
|
|
136
|
-
- Run long running commands in background using screen instead of "&".
|
|
137
|
-
- Use longer wait_for_seconds if the command is expected to run for a long time.
|
|
138
|
-
- Do not use 'cat' to read files, use ReadFiles tool instead.
|
|
139
|
-
""",
|
|
140
|
-
),
|
|
141
|
-
ToolParam(
|
|
142
|
-
inputSchema=BashInteraction.model_json_schema(),
|
|
143
|
-
name="BashInteraction",
|
|
144
|
-
description=f"""
|
|
145
|
-
- Interact with running program using this tool
|
|
146
|
-
- Special keys like arrows, interrupts, enter, etc.
|
|
147
|
-
- Send text input to the running program.
|
|
148
|
-
- Send send_specials=["Enter"] to recheck status of a running program.
|
|
149
|
-
- Only one of send_text, send_specials, send_ascii should be provided.
|
|
150
|
-
- This returns within {SLEEP_TIME_MAX_S} seconds, for heavy programs keep checking status for upto 10 turns before asking user to continue checking again.
|
|
151
|
-
- 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"].
|
|
152
|
-
- Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
|
|
153
|
-
- Set longer wait_for_seconds when program is expected to run for a long time.
|
|
154
|
-
""",
|
|
155
|
-
),
|
|
156
|
-
ToolParam(
|
|
157
|
-
inputSchema=ReadFiles.model_json_schema(),
|
|
158
|
-
name="ReadFiles",
|
|
159
|
-
description="""
|
|
160
|
-
- Read full file content of one or more files.
|
|
161
|
-
- Provide absolute file paths only
|
|
162
|
-
""",
|
|
163
|
-
),
|
|
164
|
-
ToolParam(
|
|
165
|
-
inputSchema=WriteIfEmpty.model_json_schema(),
|
|
166
|
-
name="WriteIfEmpty",
|
|
167
|
-
description="""
|
|
168
|
-
- Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files.
|
|
169
|
-
- Provide absolute file path only.
|
|
170
|
-
- For editing existing files, use FileEdit instead of this tool.
|
|
171
|
-
""",
|
|
172
|
-
),
|
|
173
|
-
ToolParam(
|
|
174
|
-
inputSchema=ReadImage.model_json_schema(),
|
|
175
|
-
name="ReadImage",
|
|
176
|
-
description="Read an image from the shell.",
|
|
177
|
-
),
|
|
178
|
-
ToolParam(
|
|
179
|
-
inputSchema=ResetShell.model_json_schema(),
|
|
180
|
-
name="ResetShell",
|
|
181
|
-
description="Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.\nAlso exits the docker environment.\nYou need to call GetScreenInfo again.",
|
|
182
|
-
),
|
|
183
|
-
ToolParam(
|
|
184
|
-
inputSchema=FileEdit.model_json_schema(),
|
|
185
|
-
name="FileEdit",
|
|
186
|
-
description="""
|
|
187
|
-
- Use absolute file path only.
|
|
188
|
-
- Use SEARCH/REPLACE blocks to edit the file.
|
|
189
|
-
- 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.
|
|
190
|
-
"""
|
|
191
|
-
+ diffinstructions,
|
|
192
|
-
),
|
|
92
|
+
tools_ = [
|
|
193
93
|
ToolParam(
|
|
194
|
-
inputSchema=
|
|
195
|
-
name=
|
|
196
|
-
description=
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- Leave project path as empty string if no project path""",
|
|
200
|
-
),
|
|
94
|
+
inputSchema=tool.inputSchema,
|
|
95
|
+
name=tool.name,
|
|
96
|
+
description=tool.description,
|
|
97
|
+
)
|
|
98
|
+
for tool in TOOL_PROMPTS
|
|
201
99
|
]
|
|
202
|
-
|
|
203
|
-
tools += [
|
|
204
|
-
ToolParam(
|
|
205
|
-
inputSchema=GetScreenInfo.model_json_schema(),
|
|
206
|
-
name="GetScreenInfo",
|
|
207
|
-
description="""
|
|
208
|
-
- Important: call this first in the conversation before ScreenShot, Mouse, and Keyboard tools.
|
|
209
|
-
- Get display information of a linux os running on docker using image "ghcr.io/anthropics/anthropic-quickstarts:computer-use-demo-latest"
|
|
210
|
-
- If user hasn't provided docker image id, check using `docker ps` and provide the id.
|
|
211
|
-
- If the docker is not running, run using `docker run -d -p 6080:6080 ghcr.io/anthropics/anthropic-quickstarts:computer-use-demo-latest`
|
|
212
|
-
- Connects shell to the docker environment.
|
|
213
|
-
- Note: once this is called, the shell enters the docker environment. All bash commands will run over there.
|
|
214
|
-
""",
|
|
215
|
-
),
|
|
216
|
-
ToolParam(
|
|
217
|
-
inputSchema=ScreenShot.model_json_schema(),
|
|
218
|
-
name="ScreenShot",
|
|
219
|
-
description="""
|
|
220
|
-
- Capture screenshot of the linux os on docker.
|
|
221
|
-
- All actions on UI using mouse and keyboard return within 0.5 seconds.
|
|
222
|
-
* So if you're doing something that takes longer for UI to update like heavy page loading, keep checking UI for update using ScreenShot upto 10 turns.
|
|
223
|
-
* Notice for smallest of the loading icons to check if your action worked.
|
|
224
|
-
* After 10 turns of no change, ask user for permission to keep checking.
|
|
225
|
-
* If you don't notice even slightest of the change, it's likely you clicked on the wrong place.
|
|
226
|
-
""",
|
|
227
|
-
),
|
|
228
|
-
ToolParam(
|
|
229
|
-
inputSchema=Mouse.model_json_schema(),
|
|
230
|
-
name="Mouse",
|
|
231
|
-
description="""
|
|
232
|
-
- Interact with the linux os on docker using mouse.
|
|
233
|
-
- Uses xdotool
|
|
234
|
-
- About left_click_drag: the current mouse position will be used as the starting point, click and drag to the given x, y coordinates. Useful in things like sliders, moving things around, etc.
|
|
235
|
-
- The output of this command has the screenshot after doing this action. Use this to verify if the action was successful.
|
|
236
|
-
""",
|
|
237
|
-
),
|
|
238
|
-
ToolParam(
|
|
239
|
-
inputSchema=Keyboard.model_json_schema(),
|
|
240
|
-
name="Keyboard",
|
|
241
|
-
description="""
|
|
242
|
-
- Interact with the linux os on docker using keyboard.
|
|
243
|
-
- Emulate keyboard input to the screen
|
|
244
|
-
- Uses xdootool to send keyboard input, keys like Return, BackSpace, Escape, Page_Up, etc. can be used.
|
|
245
|
-
- Do not use it to interact with Bash tool.
|
|
246
|
-
- Make sure you've selected a text area or an editable element before sending text.
|
|
247
|
-
- The output of this command has the screenshot after doing this action. Use this to verify if the action was successful.
|
|
248
|
-
""",
|
|
249
|
-
),
|
|
250
|
-
]
|
|
251
|
-
|
|
252
|
-
return tools
|
|
100
|
+
return tools_
|
|
253
101
|
|
|
254
102
|
|
|
255
103
|
@server.call_tool() # type: ignore
|
|
256
104
|
async def handle_call_tool(
|
|
257
105
|
name: str, arguments: dict[str, Any] | None
|
|
258
106
|
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
|
107
|
+
global BASH_STATE
|
|
259
108
|
if not arguments:
|
|
260
109
|
raise ValueError("Missing arguments")
|
|
261
110
|
|
|
262
111
|
tool_type = which_tool_name(name)
|
|
112
|
+
tool_call = parse_tool_by_name(name, arguments)
|
|
263
113
|
|
|
264
114
|
try:
|
|
265
|
-
|
|
266
|
-
except ValidationError:
|
|
267
|
-
|
|
268
|
-
def try_json(x: str) -> Any:
|
|
269
|
-
if not isinstance(x, str):
|
|
270
|
-
return x
|
|
271
|
-
try:
|
|
272
|
-
return json.loads(x)
|
|
273
|
-
except json.JSONDecodeError:
|
|
274
|
-
return x
|
|
275
|
-
|
|
276
|
-
tool_call = tool_type(**{k: try_json(v) for k, v in arguments.items()})
|
|
277
|
-
|
|
278
|
-
try:
|
|
115
|
+
assert BASH_STATE
|
|
279
116
|
output_or_dones, _ = get_tool_output(
|
|
280
|
-
|
|
117
|
+
Context(BASH_STATE, BASH_STATE.console),
|
|
118
|
+
tool_call,
|
|
119
|
+
default_enc,
|
|
120
|
+
0.0,
|
|
121
|
+
lambda x, y: ("", 0),
|
|
122
|
+
8000,
|
|
281
123
|
)
|
|
282
124
|
|
|
283
125
|
except Exception as e:
|
|
@@ -285,7 +127,6 @@ async def handle_call_tool(
|
|
|
285
127
|
|
|
286
128
|
content: list[types.TextContent | types.ImageContent | types.EmbeddedResource] = []
|
|
287
129
|
for output_or_done in output_or_dones:
|
|
288
|
-
assert not isinstance(output_or_done, DoneFlag)
|
|
289
130
|
if isinstance(output_or_done, str):
|
|
290
131
|
if issubclass(tool_type, Initialize):
|
|
291
132
|
output_or_done += """
|
|
@@ -307,31 +148,30 @@ Initialize call done.
|
|
|
307
148
|
return content
|
|
308
149
|
|
|
309
150
|
|
|
310
|
-
|
|
311
|
-
global COMPUTER_USE_ON_DOCKER_ENABLED
|
|
151
|
+
BASH_STATE = None
|
|
312
152
|
|
|
313
|
-
tools.TIMEOUT = SLEEP_TIME_MAX_S
|
|
314
|
-
tools.TIMEOUT_WHILE_OUTPUT = 55
|
|
315
|
-
tools.OUTPUT_WAIT_PATIENCE = 5
|
|
316
|
-
tools.console = Console()
|
|
317
|
-
|
|
318
|
-
if computer_use:
|
|
319
|
-
COMPUTER_USE_ON_DOCKER_ENABLED = True
|
|
320
153
|
|
|
154
|
+
async def main() -> None:
|
|
155
|
+
global BASH_STATE
|
|
156
|
+
CONFIG.update(3, 55, 5)
|
|
321
157
|
version = str(importlib.metadata.version("wcgw"))
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
158
|
+
home_dir = os.path.expanduser("~")
|
|
159
|
+
with BashState(
|
|
160
|
+
Console(), home_dir, None, None, None, None, True, None
|
|
161
|
+
) as BASH_STATE:
|
|
162
|
+
BASH_STATE.console.log("wcgw version: " + version)
|
|
163
|
+
# Run the server using stdin/stdout streams
|
|
164
|
+
async with mcp_wcgw.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
165
|
+
await server.run(
|
|
166
|
+
read_stream,
|
|
167
|
+
write_stream,
|
|
168
|
+
InitializationOptions(
|
|
169
|
+
server_name="wcgw",
|
|
170
|
+
server_version=version,
|
|
171
|
+
capabilities=server.get_capabilities(
|
|
172
|
+
notification_options=NotificationOptions(),
|
|
173
|
+
experimental_capabilities={},
|
|
174
|
+
),
|
|
334
175
|
),
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
)
|
|
176
|
+
raise_exceptions=False,
|
|
177
|
+
)
|
wcgw/client/modes.py
CHANGED
|
@@ -61,7 +61,7 @@ You are now running in "code_writer" mode.
|
|
|
61
61
|
if allowed_file_edit_globs != "all":
|
|
62
62
|
if allowed_file_edit_globs:
|
|
63
63
|
path_prompt = f"""
|
|
64
|
-
- You are allowed to run FileEdit for files matching only the following globs: {
|
|
64
|
+
- You are allowed to run FileEdit for files matching only the following globs: {", ".join(allowed_file_edit_globs)}
|
|
65
65
|
"""
|
|
66
66
|
else:
|
|
67
67
|
path_prompt = """
|
|
@@ -76,7 +76,7 @@ You are now running in "code_writer" mode.
|
|
|
76
76
|
if all_write_new_globs != "all":
|
|
77
77
|
if all_write_new_globs:
|
|
78
78
|
path_prompt = f"""
|
|
79
|
-
- You are allowed to run WriteIfEmpty files matching only the following globs: {
|
|
79
|
+
- You are allowed to run WriteIfEmpty files matching only the following globs: {", ".join(allowed_file_edit_globs)}
|
|
80
80
|
"""
|
|
81
81
|
else:
|
|
82
82
|
path_prompt = """
|
|
@@ -85,7 +85,7 @@ You are now running in "code_writer" mode.
|
|
|
85
85
|
base += path_prompt
|
|
86
86
|
|
|
87
87
|
run_command_common = """
|
|
88
|
-
- Do not use Ctrl-c
|
|
88
|
+
- Do not use Ctrl-c interrupt commands without asking the user, because often the programs don't show any update but they still are running.
|
|
89
89
|
- Do not use echo to write multi-line files, always use FileEdit tool to update a code.
|
|
90
90
|
- Do not provide code snippets unless asked by the user, instead directly add/edit the code.
|
|
91
91
|
- You should use the provided bash execution, reading and writing file tools to complete objective.
|
|
@@ -101,7 +101,7 @@ You are now running in "code_writer" mode.
|
|
|
101
101
|
if allowed_commands != "all":
|
|
102
102
|
if allowed_commands:
|
|
103
103
|
command_prompt = f"""
|
|
104
|
-
- You are only allowed to run the following commands: {
|
|
104
|
+
- You are only allowed to run the following commands: {", ".join(allowed_commands)}
|
|
105
105
|
{run_command_common}
|
|
106
106
|
"""
|
|
107
107
|
else:
|
|
@@ -124,7 +124,7 @@ Instructions:
|
|
|
124
124
|
- Do not provide code snippets unless asked by the user, instead directly add/edit the code.
|
|
125
125
|
- Do not install new tools/packages before ensuring no such tools/package or an alternative already exists.
|
|
126
126
|
- 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 wcgw tools
|
|
127
|
-
- Do not use Ctrl-c or
|
|
127
|
+
- Do not use Ctrl-c or interrupt commands without asking the user, because often the programs don't show any update but they still are running.
|
|
128
128
|
- Do not use echo to write multi-line files, always use FileEdit tool to update a code.
|
|
129
129
|
|
|
130
130
|
Additional instructions:
|
|
@@ -138,10 +138,11 @@ ARCHITECT_PROMPT = """You are now running in "architect" mode. This means
|
|
|
138
138
|
- You are not allowed to edit or update any file. You are not allowed to create any file.
|
|
139
139
|
- You are not allowed to run any commands that may change disk, system configuration, packages or environment. Only read-only commands are allowed.
|
|
140
140
|
- Only run commands that allows you to explore the repository, understand the system or read anything of relevance.
|
|
141
|
-
- Do not use Ctrl-c or
|
|
141
|
+
- Do not use Ctrl-c or interrupt commands without asking the user, because often the programs don't show any update but they still are running.
|
|
142
142
|
- You are not allowed to change directory (bash will run in -r mode)
|
|
143
|
+
- Share only snippets when any implementation is requested.
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
Respond only after doing the following:
|
|
145
146
|
- Read as many relevant files as possible.
|
|
146
147
|
- Be comprehensive in your understanding and search of relevant files.
|
|
147
148
|
- First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
|
|
@@ -149,17 +150,17 @@ Your response should be in self-critique and brainstorm style.
|
|
|
149
150
|
|
|
150
151
|
|
|
151
152
|
DEFAULT_MODES: dict[Modes, ModeImpl] = {
|
|
152
|
-
|
|
153
|
+
"wcgw": ModeImpl(
|
|
153
154
|
bash_command_mode=BashCommandMode("normal_mode", "all"),
|
|
154
155
|
write_if_empty_mode=WriteIfEmptyMode("all"),
|
|
155
156
|
file_edit_mode=FileEditMode("all"),
|
|
156
157
|
),
|
|
157
|
-
|
|
158
|
+
"architect": ModeImpl(
|
|
158
159
|
bash_command_mode=BashCommandMode("restricted_mode", "all"),
|
|
159
160
|
write_if_empty_mode=WriteIfEmptyMode([]),
|
|
160
161
|
file_edit_mode=FileEditMode([]),
|
|
161
162
|
),
|
|
162
|
-
|
|
163
|
+
"code_writer": ModeImpl(
|
|
163
164
|
bash_command_mode=BashCommandMode("normal_mode", "all"),
|
|
164
165
|
write_if_empty_mode=WriteIfEmptyMode("all"),
|
|
165
166
|
file_edit_mode=FileEditMode("all"),
|
|
@@ -172,11 +173,11 @@ def modes_to_state(
|
|
|
172
173
|
) -> tuple[BashCommandMode, FileEditMode, WriteIfEmptyMode, Modes]:
|
|
173
174
|
# First get default mode config
|
|
174
175
|
if isinstance(mode, str):
|
|
175
|
-
mode_impl = DEFAULT_MODES[
|
|
176
|
-
mode_name =
|
|
176
|
+
mode_impl = DEFAULT_MODES[mode] # converts str to Modes enum
|
|
177
|
+
mode_name: Modes = mode
|
|
177
178
|
else:
|
|
178
179
|
# For CodeWriterMode, use code_writer as base and override
|
|
179
|
-
mode_impl = DEFAULT_MODES[
|
|
180
|
+
mode_impl = DEFAULT_MODES["code_writer"]
|
|
180
181
|
# Override with custom settings from CodeWriterMode
|
|
181
182
|
mode_impl = ModeImpl(
|
|
182
183
|
bash_command_mode=BashCommandMode(
|
|
@@ -186,7 +187,7 @@ def modes_to_state(
|
|
|
186
187
|
file_edit_mode=FileEditMode(mode.allowed_globs),
|
|
187
188
|
write_if_empty_mode=WriteIfEmptyMode(mode.allowed_globs),
|
|
188
189
|
)
|
|
189
|
-
mode_name =
|
|
190
|
+
mode_name = "code_writer"
|
|
190
191
|
return (
|
|
191
192
|
mode_impl.bash_command_mode,
|
|
192
193
|
mode_impl.file_edit_mode,
|
|
@@ -233,10 +234,4 @@ Provide all relevant file paths in order to understand and solve the the task. E
|
|
|
233
234
|
(Note to self: this conversation can then be resumed later asking "Resume wcgw task `<generated id>`" which should call Initialize tool)
|
|
234
235
|
"""
|
|
235
236
|
|
|
236
|
-
KTS = {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def get_kt_prompt() -> str:
|
|
240
|
-
from .tools import BASH_STATE
|
|
241
|
-
|
|
242
|
-
return KTS[BASH_STATE.mode]
|
|
237
|
+
KTS = {"wcgw": WCGW_KT, "architect": ARCHITECT_KT, "code_writer": WCGW_KT}
|