wcgw 0.1.2__tar.gz → 0.2.0__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: What could go wrong giving full shell access to chatgpt?
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -19,6 +19,7 @@ Instructions for `Execute Bash`:
19
19
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
20
20
  - The first line might be `(...truncated)` if the output is too long.
21
21
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
22
+ - 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.
22
23
 
23
24
  Instructions for `Write File`
24
25
  - Write content to a file. Provide file path and content. Use this instead of ExecuteBash for writing files.
@@ -28,4 +29,10 @@ Instructions for `Write File`
28
29
  Always critically think and debate with yourself to solve the problem. Understand the context and the code by reading as much resources as possible before writing a single piece of code.
29
30
 
30
31
  ---
31
- Ask the user for the user_id `UUID` if they haven't provided in the first message.
32
+ Ask the user for the user_id `UUID` if they haven't provided in the first message.
33
+
34
+ ---
35
+ Error references:
36
+ 1. "Input should be a valid integer"
37
+ You are probably send_ascii command a string. If you are in REPL mode, use `execute_command` instead.
38
+ Otherwise convert the string into sequence of ascii integers.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "0.1.2"
4
+ version = "0.2.0"
5
5
  description = "What could go wrong giving full shell access to chatgpt?"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10, <3.13"
@@ -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(
@@ -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": "#@@"}}, # type: ignore[arg-type]
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('export PS1="#@@"')
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
- SHELL.expect("#@@")
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(["#@@", pexpect.TIMEOUT], timeout=wait)
194
- running = ""
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[len(running) :])
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
- return text + f"\n{last_line}", 0
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}'"
@@ -876,7 +876,7 @@ wheels = [
876
876
 
877
877
  [[package]]
878
878
  name = "wcgw"
879
- version = "0.0.10"
879
+ version = "0.1.2"
880
880
  source = { editable = "." }
881
881
  dependencies = [
882
882
  { name = "fastapi" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes