wcgw 0.1.2__py3-none-any.whl → 0.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.
wcgw/basic.py
CHANGED
|
@@ -159,7 +159,7 @@ def loop(
|
|
|
159
159
|
ExecuteBash,
|
|
160
160
|
description="""
|
|
161
161
|
- Execute a bash script. This is stateful (beware with subsequent calls).
|
|
162
|
-
- Execute commands using `execute_command` attribute.
|
|
162
|
+
- Execute commands using `execute_command` attribute. You can run python/node/other REPL code lines using `execute_command` too.
|
|
163
163
|
- Do not use interactive commands like nano. Prefer writing simpler commands.
|
|
164
164
|
- Last line will always be `(exit <int code>)` except if
|
|
165
165
|
- The last line is `(pending)` if the program is still running or waiting for your input. You can then send input using `send_ascii` attributes. You get status by sending new line `send_ascii: ["Enter"]` or `send_ascii: [10]`.
|
|
@@ -167,6 +167,7 @@ def loop(
|
|
|
167
167
|
- Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
|
|
168
168
|
- The first line might be `(...truncated)` if the output is too long.
|
|
169
169
|
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
170
|
+
- You can run python/node/other REPL code lines using `execute_command` too. NOTE: `execute_command` doesn't create a new shell, it uses the same shell.
|
|
170
171
|
""",
|
|
171
172
|
),
|
|
172
173
|
openai.pydantic_function_tool(
|
wcgw/tools.py
CHANGED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import base64
|
|
3
3
|
import json
|
|
4
4
|
import mimetypes
|
|
5
|
+
import re
|
|
5
6
|
import sys
|
|
6
7
|
import threading
|
|
7
8
|
import traceback
|
|
@@ -77,17 +78,20 @@ class Writefile(BaseModel):
|
|
|
77
78
|
file_content: str
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
PROMPT = "#@@"
|
|
82
|
+
|
|
83
|
+
|
|
80
84
|
def start_shell() -> pexpect.spawn:
|
|
81
85
|
SHELL = pexpect.spawn(
|
|
82
86
|
"/bin/bash --noprofile --norc",
|
|
83
|
-
env={**os.environ, **{"PS1":
|
|
87
|
+
env={**os.environ, **{"PS1": PROMPT}}, # type: ignore[arg-type]
|
|
84
88
|
echo=False,
|
|
85
89
|
encoding="utf-8",
|
|
86
90
|
timeout=TIMEOUT,
|
|
87
91
|
)
|
|
88
|
-
SHELL.expect(
|
|
92
|
+
SHELL.expect(PROMPT)
|
|
89
93
|
SHELL.sendline("stty -icanon -echo")
|
|
90
|
-
SHELL.expect(
|
|
94
|
+
SHELL.expect(PROMPT)
|
|
91
95
|
return SHELL
|
|
92
96
|
|
|
93
97
|
|
|
@@ -103,16 +107,22 @@ def _is_int(mystr: str) -> bool:
|
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
def _get_exit_code() -> int:
|
|
110
|
+
if PROMPT != "#@@":
|
|
111
|
+
return 0
|
|
106
112
|
# First reset the prompt in case venv was sourced or other reasons.
|
|
107
|
-
SHELL.sendline(
|
|
108
|
-
SHELL.expect(
|
|
113
|
+
SHELL.sendline(f"export PS1={PROMPT}")
|
|
114
|
+
SHELL.expect(PROMPT)
|
|
109
115
|
# Reset echo also if it was enabled
|
|
110
116
|
SHELL.sendline("stty -icanon -echo")
|
|
111
|
-
SHELL.expect(
|
|
117
|
+
SHELL.expect(PROMPT)
|
|
112
118
|
SHELL.sendline("echo $?")
|
|
113
119
|
before = ""
|
|
114
120
|
while not _is_int(before): # Consume all previous output
|
|
115
|
-
|
|
121
|
+
try:
|
|
122
|
+
SHELL.expect(PROMPT)
|
|
123
|
+
except pexpect.TIMEOUT:
|
|
124
|
+
print(f"Couldn't get exit code, before: {before}")
|
|
125
|
+
raise
|
|
116
126
|
assert isinstance(SHELL.before, str)
|
|
117
127
|
# Render because there could be some anscii escape sequences still set like in google colab env
|
|
118
128
|
before = render_terminal_output(SHELL.before).strip()
|
|
@@ -136,17 +146,51 @@ BASH_STATE: BASH_CLF_OUTPUT = "running"
|
|
|
136
146
|
|
|
137
147
|
|
|
138
148
|
WAITING_INPUT_MESSAGE = """A command is already running waiting for input. NOTE: You can't run multiple shell sessions, likely a previous program hasn't exited.
|
|
139
|
-
1. Get its output using `send_ascii: [10]`
|
|
149
|
+
1. Get its output using `send_ascii: [10] or send_ascii: ["Enter"]`
|
|
140
150
|
2. Use `send_ascii` to give inputs to the running program, don't use `execute_command` OR
|
|
141
151
|
3. kill the previous program by sending ctrl+c first using `send_ascii`"""
|
|
142
152
|
|
|
143
153
|
|
|
154
|
+
def update_repl_prompt(command: str) -> bool:
|
|
155
|
+
global PROMPT
|
|
156
|
+
if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
|
|
157
|
+
SHELL.sendintr()
|
|
158
|
+
index = SHELL.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
159
|
+
if index == 0:
|
|
160
|
+
return False
|
|
161
|
+
before = SHELL.before or ""
|
|
162
|
+
assert before, "Something went wrong updating repl prompt"
|
|
163
|
+
PROMPT = before.split("\n")[-1].strip()
|
|
164
|
+
# Escape all regex
|
|
165
|
+
PROMPT = re.escape(PROMPT)
|
|
166
|
+
print(f"Trying to update prompt to: {PROMPT.encode()!r}")
|
|
167
|
+
index = 0
|
|
168
|
+
while index == 0:
|
|
169
|
+
# Consume all REPL prompts till now
|
|
170
|
+
index = SHELL.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
171
|
+
print(f"Prompt updated to: {PROMPT}")
|
|
172
|
+
return True
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
144
176
|
def execute_bash(
|
|
145
177
|
enc: tiktoken.Encoding, bash_arg: ExecuteBash, max_tokens: Optional[int]
|
|
146
178
|
) -> tuple[str, float]:
|
|
147
179
|
global SHELL, BASH_STATE
|
|
148
180
|
try:
|
|
181
|
+
is_interrupt = False
|
|
149
182
|
if bash_arg.execute_command:
|
|
183
|
+
updated_repl_mode = update_repl_prompt(bash_arg.execute_command)
|
|
184
|
+
if updated_repl_mode:
|
|
185
|
+
BASH_STATE = "running"
|
|
186
|
+
response = "Prompt updated, you can execute REPL lines using execute_command now"
|
|
187
|
+
console.print(response)
|
|
188
|
+
return (
|
|
189
|
+
response,
|
|
190
|
+
0,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
console.print(f"$ {bash_arg.execute_command}")
|
|
150
194
|
if BASH_STATE == "waiting_for_input":
|
|
151
195
|
raise ValueError(WAITING_INPUT_MESSAGE)
|
|
152
196
|
elif BASH_STATE == "wont_exit":
|
|
@@ -160,14 +204,14 @@ def execute_bash(
|
|
|
160
204
|
raise ValueError(
|
|
161
205
|
"Command should not contain newline character in middle. Run only one command at a time."
|
|
162
206
|
)
|
|
163
|
-
|
|
164
|
-
console.print(f"$ {command}")
|
|
165
207
|
SHELL.sendline(command)
|
|
166
208
|
elif bash_arg.send_ascii:
|
|
167
209
|
console.print(f"Sending ASCII sequence: {bash_arg.send_ascii}")
|
|
168
210
|
for char in bash_arg.send_ascii:
|
|
169
211
|
if isinstance(char, int):
|
|
170
212
|
SHELL.send(chr(char))
|
|
213
|
+
if char == 3:
|
|
214
|
+
is_interrupt = True
|
|
171
215
|
if char == "Key-up":
|
|
172
216
|
SHELL.send("\033[A")
|
|
173
217
|
elif char == "Key-down":
|
|
@@ -180,6 +224,7 @@ def execute_bash(
|
|
|
180
224
|
SHELL.send("\n")
|
|
181
225
|
elif char == "Ctrl-c":
|
|
182
226
|
SHELL.sendintr()
|
|
227
|
+
is_interrupt = True
|
|
183
228
|
else:
|
|
184
229
|
raise Exception("Nothing to send")
|
|
185
230
|
BASH_STATE = "running"
|
|
@@ -190,16 +235,11 @@ def execute_bash(
|
|
|
190
235
|
raise
|
|
191
236
|
|
|
192
237
|
wait = 5
|
|
193
|
-
index = SHELL.expect([
|
|
194
|
-
|
|
195
|
-
while index == 1:
|
|
196
|
-
if wait > TIMEOUT:
|
|
197
|
-
raise TimeoutError("Timeout while waiting for shell prompt")
|
|
198
|
-
|
|
238
|
+
index = SHELL.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
|
|
239
|
+
if index == 1:
|
|
199
240
|
BASH_STATE = "waiting_for_input"
|
|
200
241
|
text = SHELL.before or ""
|
|
201
|
-
print(text
|
|
202
|
-
running = text
|
|
242
|
+
print(text)
|
|
203
243
|
|
|
204
244
|
text = render_terminal_output(text)
|
|
205
245
|
tokens = enc.encode(text)
|
|
@@ -208,7 +248,21 @@ def execute_bash(
|
|
|
208
248
|
text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
|
|
209
249
|
|
|
210
250
|
last_line = "(pending)"
|
|
211
|
-
|
|
251
|
+
text = text + f"\n{last_line}"
|
|
252
|
+
|
|
253
|
+
if is_interrupt:
|
|
254
|
+
text = (
|
|
255
|
+
text
|
|
256
|
+
+ """
|
|
257
|
+
Failure interrupting. Have you entered a new REPL like python, node, ipython, etc.? Or have you exited from a previous REPL program?
|
|
258
|
+
If yes:
|
|
259
|
+
Run execute_command: "wcgw_update_prompt()" to enter the new REPL mode.
|
|
260
|
+
If no:
|
|
261
|
+
Try Ctrl-c or Ctrl-d again.
|
|
262
|
+
"""
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return text, 0
|
|
212
266
|
|
|
213
267
|
assert isinstance(SHELL.before, str)
|
|
214
268
|
output = render_terminal_output(SHELL.before)
|
|
@@ -284,7 +338,7 @@ def ensure_no_previous_output(func: Callable[Param, T]) -> Callable[Param, T]:
|
|
|
284
338
|
def read_image_from_shell(file_path: str) -> ImageData:
|
|
285
339
|
if not os.path.isabs(file_path):
|
|
286
340
|
SHELL.sendline("pwd")
|
|
287
|
-
SHELL.expect(
|
|
341
|
+
SHELL.expect(PROMPT)
|
|
288
342
|
assert isinstance(SHELL.before, str)
|
|
289
343
|
current_dir = render_terminal_output(SHELL.before).strip()
|
|
290
344
|
file_path = os.path.join(current_dir, file_path)
|
|
@@ -303,7 +357,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
|
|
|
303
357
|
def write_file(writefile: Writefile) -> str:
|
|
304
358
|
if not os.path.isabs(writefile.file_path):
|
|
305
359
|
SHELL.sendline("pwd")
|
|
306
|
-
SHELL.expect(
|
|
360
|
+
SHELL.expect(PROMPT)
|
|
307
361
|
assert isinstance(SHELL.before, str)
|
|
308
362
|
current_dir = render_terminal_output(SHELL.before).strip()
|
|
309
363
|
return f"Failure: Use absolute path only. FYI current working directory is '{current_dir}'"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
wcgw/__init__.py,sha256=okSsOWpTKDjEQzgOin3Kdpx4Mc3MFX1RunjopHQSIWE,62
|
|
2
2
|
wcgw/__main__.py,sha256=MjJnFwfYzA1rW47xuSP1EVsi53DTHeEGqESkQwsELFQ,34
|
|
3
|
-
wcgw/basic.py,sha256=
|
|
3
|
+
wcgw/basic.py,sha256=aTos3c0URl-ufgXfQ1bkg-5oFCR_SxG_VI5qckBtex0,16426
|
|
4
4
|
wcgw/claude.py,sha256=Bp45-UMBIJd-4tzX618nu-SpRbVtkTb1Es6c_gW6xy0,14861
|
|
5
5
|
wcgw/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
|
|
6
6
|
wcgw/openai_adapters.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
wcgw/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
|
|
8
|
-
wcgw/tools.py,sha256=
|
|
9
|
-
wcgw-0.
|
|
10
|
-
wcgw-0.
|
|
11
|
-
wcgw-0.
|
|
12
|
-
wcgw-0.
|
|
8
|
+
wcgw/tools.py,sha256=UdSU6lAbOGNdG2wiM5x8YTosBDlphiMEo7MHtMjGvRk,18618
|
|
9
|
+
wcgw-0.2.0.dist-info/METADATA,sha256=X4vyv9Oaq8JtD301hk8jObC3bHggqtyWcxNvUssG-I4,5076
|
|
10
|
+
wcgw-0.2.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
+
wcgw-0.2.0.dist-info/entry_points.txt,sha256=T-IH7w6Vc650hr8xksC8kJfbJR4uwN8HDudejwDwrNM,59
|
|
12
|
+
wcgw-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|