AdithyaSK HF Staff commited on
Commit
819003c
Β·
verified Β·
1 Parent(s): 32df48d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. server/gradio_ui.py +450 -145
server/gradio_ui.py CHANGED
@@ -14,10 +14,70 @@ from typing import Any, Optional
14
  import gradio as gr
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def _lines(value: str) -> list[str]:
18
  return [line.strip() for line in value.splitlines() if line.strip()]
19
 
20
 
 
 
 
 
21
  def _extract_tool_text(result: dict[str, Any]) -> str:
22
  obs = result.get("observation", {})
23
  if not isinstance(obs, dict):
@@ -33,6 +93,46 @@ def _extract_tool_text(result: dict[str, Any]) -> str:
33
  return str(tool_result.get("data", ""))
34
 
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  def coding_tools_ui_builder(
37
  web_manager,
38
  action_fields: list[dict[str, Any]],
@@ -47,171 +147,376 @@ def coding_tools_ui_builder(
47
  except RuntimeError:
48
  return {}
49
 
50
- with gr.Blocks(title=f"{title} - Coding Tools") as demo:
51
- gr.Markdown(f"# {title}")
52
- gr.Markdown("E2B-backed coding tools environment with SETA-style tool surface.")
 
 
 
 
 
 
 
 
53
 
54
  with gr.Row():
55
- with gr.Column(scale=1):
56
- gr.Markdown("### Session")
57
- setup_input = gr.Textbox(
58
- label="Setup commands",
59
- value="mkdir -p /home/user/work",
60
- lines=4,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  )
62
- verify_input = gr.Textbox(
63
- label="Verify commands",
64
- value="pytest -q",
65
- lines=3,
66
  )
67
- reset_btn = gr.Button("Reset sandbox", variant="primary")
68
- close_btn = gr.Button("Stop / Close session", variant="secondary")
69
-
70
- gr.Markdown("### Bash")
71
- bash_input = gr.Textbox(label="Command", value="pwd && ls -la", lines=3)
72
- timeout_input = gr.Number(label="Timeout (seconds)", value=30)
73
- bash_btn = gr.Button("Run bash", variant="primary")
74
-
75
- gr.Markdown("### Submit")
76
- submit_btn = gr.Button("Submit solution", variant="primary")
77
-
78
- with gr.Column(scale=1):
79
- gr.Markdown("### File tools")
80
- file_path = gr.Textbox(label="File path", value="/home/user/work/main.py")
81
- read_btn = gr.Button("Read")
82
- write_content = gr.Textbox(label="Write content", lines=5)
83
- write_btn = gr.Button("Write")
84
- old_string = gr.Textbox(label="Old string", lines=2)
85
- new_string = gr.Textbox(label="New string", lines=2)
86
- replace_all = gr.Checkbox(label="Replace all", value=False)
87
- edit_btn = gr.Button("Edit")
88
-
89
- gr.Markdown("### Search tools")
90
- glob_pattern = gr.Textbox(label="Glob pattern", value="**/*.py")
91
- glob_path = gr.Textbox(label="Glob path", value="/home/user/work")
92
- glob_btn = gr.Button("Glob")
93
- grep_pattern = gr.Textbox(label="Grep pattern", value="TODO")
94
- grep_path = gr.Textbox(label="Grep path", value="/home/user/work")
95
- grep_include = gr.Textbox(label="Grep include (optional)")
96
- grep_btn = gr.Button("Grep")
97
-
98
- with gr.Column(scale=1):
99
- gr.Markdown("### Directory and Todo")
100
- ls_path = gr.Textbox(label="LS path", value="/home/user/work")
101
- ls_ignore = gr.Textbox(label="LS ignore (comma-separated)")
102
- ls_btn = gr.Button("LS")
103
- todos_json = gr.Code(
104
- label="Todos JSON",
105
- language="json",
106
- value='[{"id":"1","content":"Inspect files","status":"in_progress","priority":"high"}]',
107
- lines=8,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  )
109
- todo_btn = gr.Button("Update todos")
110
- state_view = gr.JSON(label="Session state", value={})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- output = gr.Textbox(label="Tool output", lines=12, interactive=False)
113
- status = gr.Textbox(label="Status", value="Ready.", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
 
115
  async def on_reset(setup_text: str, verify_text: str):
116
- result = await web_manager.reset_environment(
117
  {"setup": _lines(setup_text), "verify": _lines(verify_text)}
118
  )
119
  state = state_payload()
120
- return state, json.dumps(result, indent=2), "Sandbox reset."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  async def on_close():
123
  await web_manager._run_sync_in_thread_pool(web_manager.env.close)
124
- return {}, "Session closed.", "Session closed."
125
-
126
- async def on_bash(command: str, timeout: float):
127
- result = await web_manager.step_environment(
128
- {
129
- "tool_name": "bash",
130
- "arguments": {"command": command, "timeout": timeout},
131
- }
132
- )
133
- return state_payload(), _extract_tool_text(result), "bash executed."
134
-
135
- async def on_read(path: str):
136
- result = await web_manager.step_environment(
137
- {"tool_name": "read", "arguments": {"file_path": path}}
138
  )
139
- return state_payload(), _extract_tool_text(result), "read executed."
140
-
141
- async def on_write(path: str, content: str):
142
- result = await web_manager.step_environment(
143
- {
144
- "tool_name": "write",
145
- "arguments": {"file_path": path, "content": content},
146
- }
147
- )
148
- return state_payload(), _extract_tool_text(result), "write executed."
149
-
150
- async def on_edit(path: str, old: str, new: str, all_matches: bool):
151
- result = await web_manager.step_environment(
152
- {
153
- "tool_name": "edit",
154
- "arguments": {
155
- "file_path": path,
156
- "old_string": old,
157
- "new_string": new,
158
- "replace_all": all_matches,
159
- },
160
- }
161
- )
162
- return state_payload(), _extract_tool_text(result), "edit executed."
163
 
164
- async def on_glob(pattern: str, path: str):
165
- result = await web_manager.step_environment(
166
- {"tool_name": "glob", "arguments": {"pattern": pattern, "path": path}}
167
- )
168
- return state_payload(), _extract_tool_text(result), "glob executed."
169
-
170
- async def on_grep(pattern: str, path: str, include: str):
171
- args: dict[str, Any] = {"pattern": pattern, "path": path}
172
- if include.strip():
173
- args["include"] = include
174
- result = await web_manager.step_environment(
175
- {"tool_name": "grep", "arguments": args}
176
- )
177
- return state_payload(), _extract_tool_text(result), "grep executed."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
- async def on_ls(path: str, ignore_csv: str):
180
- ignore = [item.strip() for item in ignore_csv.split(",") if item.strip()]
181
- result = await web_manager.step_environment(
182
- {"tool_name": "ls", "arguments": {"path": path, "ignore": ignore or None}}
183
  )
184
- return state_payload(), _extract_tool_text(result), "ls executed."
 
 
185
 
186
- async def on_todo(todo_raw: str):
187
- todos = json.loads(todo_raw)
188
- result = await web_manager.step_environment(
189
- {"tool_name": "todo_write", "arguments": {"todos": todos}}
190
- )
191
- return state_payload(), _extract_tool_text(result), "todo_write executed."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
- async def on_submit():
194
- result = await web_manager.step_environment(
195
- {"tool_name": "submit_solution", "arguments": {}}
196
- )
197
- return state_payload(), _extract_tool_text(result), "submit_solution executed."
198
-
199
- reset_btn.click(on_reset, [setup_input, verify_input], [state_view, output, status])
200
- close_btn.click(on_close, outputs=[state_view, output, status])
201
- bash_btn.click(on_bash, [bash_input, timeout_input], [state_view, output, status])
202
- read_btn.click(on_read, [file_path], [state_view, output, status])
203
- write_btn.click(on_write, [file_path, write_content], [state_view, output, status])
204
- edit_btn.click(
205
- on_edit,
206
- [file_path, old_string, new_string, replace_all],
207
- [state_view, output, status],
208
- )
209
- glob_btn.click(on_glob, [glob_pattern, glob_path], [state_view, output, status])
210
- grep_btn.click(
211
- on_grep, [grep_pattern, grep_path, grep_include], [state_view, output, status]
212
  )
213
- ls_btn.click(on_ls, [ls_path, ls_ignore], [state_view, output, status])
214
- todo_btn.click(on_todo, [todos_json], [state_view, output, status])
215
- submit_btn.click(on_submit, outputs=[state_view, output, status])
216
 
217
  return demo
 
14
  import gradio as gr
15
 
16
 
17
+ TOOL_SPECS: dict[str, dict[str, Any]] = {
18
+ "bash": {
19
+ "icon": "πŸ–₯️",
20
+ "description": "Execute a bash command inside the E2B sandbox.",
21
+ "signature": "bash(command: str, timeout: float = 30)",
22
+ },
23
+ "read": {
24
+ "icon": "πŸ“–",
25
+ "description": "Read a file from the sandbox. Optional `offset` and `limit` slice the lines.",
26
+ "signature": "read(file_path: str, offset?: int, limit?: int)",
27
+ },
28
+ "write": {
29
+ "icon": "✏️",
30
+ "description": "Create or overwrite a file with `content`.",
31
+ "signature": "write(file_path: str, content: str)",
32
+ },
33
+ "edit": {
34
+ "icon": "πŸ”§",
35
+ "description": "Find-and-replace `old_string` with `new_string` in a file. Use `replace_all` for every occurrence.",
36
+ "signature": "edit(file_path, old_string, new_string, replace_all=False)",
37
+ },
38
+ "multi_edit": {
39
+ "icon": "πŸ”",
40
+ "description": "Apply a list of {old_string, new_string, replace_all?} edits sequentially in one file.",
41
+ "signature": "multi_edit(file_path, edits: list[dict])",
42
+ },
43
+ "glob": {
44
+ "icon": "πŸ”",
45
+ "description": "Glob for files matching a shell-style pattern, optionally rooted at `path`.",
46
+ "signature": "glob(pattern: str, path?: str)",
47
+ },
48
+ "grep": {
49
+ "icon": "🧡",
50
+ "description": "Regex search across files. `include` is an optional file glob filter.",
51
+ "signature": "grep(pattern: str, path?: str, include?: str)",
52
+ },
53
+ "ls": {
54
+ "icon": "πŸ“‚",
55
+ "description": "List a directory. `ignore` is a comma-separated list of patterns to skip.",
56
+ "signature": "ls(path: str = '.', ignore?: list[str])",
57
+ },
58
+ "todo_write": {
59
+ "icon": "πŸ—’οΈ",
60
+ "description": "Replace the agent's todo list. Each item: {id, content, status, priority}. Only one `in_progress` at a time.",
61
+ "signature": "todo_write(todos: list[dict])",
62
+ },
63
+ "submit_solution": {
64
+ "icon": "πŸš€",
65
+ "description": "Run the configured verify commands and finalise the episode with a reward.",
66
+ "signature": "submit_solution()",
67
+ },
68
+ }
69
+
70
+ TOOL_CHOICES = [(f"{spec['icon']} {name}", name) for name, spec in TOOL_SPECS.items()]
71
+
72
+
73
  def _lines(value: str) -> list[str]:
74
  return [line.strip() for line in value.splitlines() if line.strip()]
75
 
76
 
77
+ def _csv_list(value: str) -> list[str]:
78
+ return [item.strip() for item in value.split(",") if item.strip()]
79
+
80
+
81
  def _extract_tool_text(result: dict[str, Any]) -> str:
82
  obs = result.get("observation", {})
83
  if not isinstance(obs, dict):
 
93
  return str(tool_result.get("data", ""))
94
 
95
 
96
+ def _extract_tool_error(result: dict[str, Any]) -> bool:
97
+ obs = result.get("observation", {})
98
+ if not isinstance(obs, dict):
99
+ return False
100
+ tool_result = obs.get("result", {})
101
+ if not isinstance(tool_result, dict):
102
+ return False
103
+ return bool(tool_result.get("is_error", False))
104
+
105
+
106
+ def _format_status(state: dict[str, Any]) -> str:
107
+ if not state:
108
+ return "**No active session.** Configure setup/verify and click *Reset sandbox*."
109
+ sandbox_id = state.get("sandbox_id") or "β€”"
110
+ step_count = state.get("step_count", 0)
111
+ submitted = state.get("submitted", False)
112
+ last_reward = state.get("last_reward")
113
+ reward_str = "β€”" if last_reward is None else f"{last_reward}"
114
+ submitted_str = "βœ… submitted" if submitted else "⏳ in progress"
115
+ return (
116
+ f"**Session active** Β· sandbox `{sandbox_id}` Β· "
117
+ f"steps `{step_count}` Β· {submitted_str} Β· reward `{reward_str}`"
118
+ )
119
+
120
+
121
+ def _format_history(state: dict[str, Any]) -> list[list[str]]:
122
+ history = state.get("tool_history", []) or []
123
+ rows: list[list[str]] = []
124
+ for entry in history[-25:][::-1]:
125
+ if not isinstance(entry, dict):
126
+ continue
127
+ tool = str(entry.get("tool", ""))
128
+ ok = "βœ…" if entry.get("ok") else "❌"
129
+ output = str(entry.get("output") or entry.get("error") or "")
130
+ if len(output) > 140:
131
+ output = output[:137] + "…"
132
+ rows.append([ok, tool, output])
133
+ return rows
134
+
135
+
136
  def coding_tools_ui_builder(
137
  web_manager,
138
  action_fields: list[dict[str, Any]],
 
147
  except RuntimeError:
148
  return {}
149
 
150
+ with gr.Blocks(
151
+ title=f"{title} - Coding Tools",
152
+ theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),
153
+ ) as demo:
154
+ gr.Markdown(f"# 🧰 {title}")
155
+ gr.Markdown(
156
+ "E2B-backed coding environment exposing a SETA-style tool surface. "
157
+ "Pick a tool, fill the inputs, and inspect the output, state, and call history."
158
+ )
159
+
160
+ status_md = gr.Markdown(_format_status({}))
161
 
162
  with gr.Row():
163
+ # ───────── Left: Session controls ─────────
164
+ with gr.Column(scale=1, min_width=320):
165
+ with gr.Accordion("Session controls", open=True):
166
+ setup_input = gr.Textbox(
167
+ label="Setup commands (one per line)",
168
+ value="mkdir -p /home/user/work",
169
+ lines=3,
170
+ )
171
+ verify_input = gr.Textbox(
172
+ label="Verify commands (one per line)",
173
+ value="pytest -q",
174
+ lines=2,
175
+ )
176
+ with gr.Row():
177
+ reset_btn = gr.Button("πŸ”„ Reset sandbox", variant="primary")
178
+ close_btn = gr.Button("πŸ›‘ Close", variant="secondary")
179
+
180
+ # ───────── Middle: Tool selection + dynamic inputs ─────────
181
+ with gr.Column(scale=2, min_width=420):
182
+ gr.Markdown("### Tool")
183
+ tool_dropdown = gr.Dropdown(
184
+ choices=TOOL_CHOICES,
185
+ value="bash",
186
+ label="Select a tool",
187
+ interactive=True,
188
  )
189
+ tool_help = gr.Markdown(
190
+ f"**{TOOL_SPECS['bash']['signature']}** \n{TOOL_SPECS['bash']['description']}"
 
 
191
  )
192
+
193
+ # One Group per tool. Visibility toggled by tool_dropdown.
194
+ with gr.Group(visible=True) as g_bash:
195
+ bash_command = gr.Textbox(
196
+ label="command", value="pwd && ls -la", lines=3
197
+ )
198
+ bash_timeout = gr.Number(label="timeout (seconds)", value=30)
199
+
200
+ with gr.Group(visible=False) as g_read:
201
+ read_path = gr.Textbox(
202
+ label="file_path", value="/home/user/work/main.py"
203
+ )
204
+ with gr.Row():
205
+ read_offset = gr.Number(label="offset (optional)", value=None)
206
+ read_limit = gr.Number(label="limit (optional)", value=None)
207
+
208
+ with gr.Group(visible=False) as g_write:
209
+ write_path = gr.Textbox(
210
+ label="file_path", value="/home/user/work/main.py"
211
+ )
212
+ write_content = gr.Textbox(label="content", lines=8)
213
+
214
+ with gr.Group(visible=False) as g_edit:
215
+ edit_path = gr.Textbox(
216
+ label="file_path", value="/home/user/work/main.py"
217
+ )
218
+ edit_old = gr.Textbox(label="old_string", lines=3)
219
+ edit_new = gr.Textbox(label="new_string", lines=3)
220
+ edit_replace_all = gr.Checkbox(label="replace_all", value=False)
221
+
222
+ with gr.Group(visible=False) as g_multi_edit:
223
+ multi_edit_path = gr.Textbox(
224
+ label="file_path", value="/home/user/work/main.py"
225
+ )
226
+ multi_edit_json = gr.Code(
227
+ label="edits (JSON array)",
228
+ language="json",
229
+ value=(
230
+ '[\n'
231
+ ' {"old_string": "TODO", "new_string": "DONE", "replace_all": false}\n'
232
+ ']'
233
+ ),
234
+ lines=8,
235
+ )
236
+
237
+ with gr.Group(visible=False) as g_glob:
238
+ glob_pattern = gr.Textbox(label="pattern", value="**/*.py")
239
+ glob_path = gr.Textbox(
240
+ label="path (optional)", value="/home/user/work"
241
+ )
242
+
243
+ with gr.Group(visible=False) as g_grep:
244
+ grep_pattern = gr.Textbox(label="pattern", value="TODO")
245
+ grep_path = gr.Textbox(
246
+ label="path (optional)", value="/home/user/work"
247
+ )
248
+ grep_include = gr.Textbox(
249
+ label="include (optional file glob)", value=""
250
+ )
251
+
252
+ with gr.Group(visible=False) as g_ls:
253
+ ls_path = gr.Textbox(label="path", value="/home/user/work")
254
+ ls_ignore = gr.Textbox(
255
+ label="ignore (comma-separated globs)", value=""
256
+ )
257
+
258
+ with gr.Group(visible=False) as g_todo:
259
+ todo_json = gr.Code(
260
+ label="todos (JSON array)",
261
+ language="json",
262
+ value=(
263
+ '[\n'
264
+ ' {"id":"1","content":"Inspect files",'
265
+ '"status":"in_progress","priority":"high"}\n'
266
+ ']'
267
+ ),
268
+ lines=8,
269
+ )
270
+
271
+ with gr.Group(visible=False) as g_submit:
272
+ gr.Markdown(
273
+ "_Submit runs the configured verify commands and ends the "
274
+ "episode with a reward._"
275
+ )
276
+
277
+ run_btn = gr.Button("▢️ Run tool", variant="primary", size="lg")
278
+
279
+ # ───────── Right: Output + State ─────────
280
+ with gr.Column(scale=2, min_width=420):
281
+ gr.Markdown("### Output")
282
+ output_status = gr.Markdown("_Awaiting first call._")
283
+ output_view = gr.Code(
284
+ label="Tool output (text)",
285
+ language=None,
286
+ value="",
287
+ lines=14,
288
  )
289
+ with gr.Accordion("Raw step response", open=False):
290
+ raw_response = gr.Code(
291
+ label="raw JSON",
292
+ language="json",
293
+ value="",
294
+ lines=14,
295
+ )
296
+
297
+ gr.Markdown("### State")
298
+ with gr.Tabs():
299
+ with gr.Tab("Summary"):
300
+ state_summary = gr.Markdown(_format_status({}))
301
+ history_table = gr.Dataframe(
302
+ headers=["βœ“", "tool", "output"],
303
+ datatype=["str", "str", "str"],
304
+ row_count=(0, "dynamic"),
305
+ wrap=True,
306
+ label="Tool history (latest 25)",
307
+ )
308
+ with gr.Tab("JSON"):
309
+ state_json = gr.Code(
310
+ label="full session state",
311
+ language="json",
312
+ value="{}",
313
+ lines=18,
314
+ )
315
+
316
+ # ───────── Tool dropdown β†’ input form visibility ─────────
317
+ TOOL_GROUPS = {
318
+ "bash": g_bash,
319
+ "read": g_read,
320
+ "write": g_write,
321
+ "edit": g_edit,
322
+ "multi_edit": g_multi_edit,
323
+ "glob": g_glob,
324
+ "grep": g_grep,
325
+ "ls": g_ls,
326
+ "todo_write": g_todo,
327
+ "submit_solution": g_submit,
328
+ }
329
+ group_components = list(TOOL_GROUPS.values())
330
 
331
+ def on_tool_change(tool: str):
332
+ spec = TOOL_SPECS.get(tool, {})
333
+ help_md = (
334
+ f"**{spec.get('signature', tool)}** \n{spec.get('description', '')}"
335
+ )
336
+ updates = [gr.update(visible=(name == tool)) for name in TOOL_GROUPS]
337
+ return [help_md, *updates]
338
+
339
+ tool_dropdown.change(
340
+ on_tool_change, inputs=[tool_dropdown], outputs=[tool_help, *group_components]
341
+ )
342
+
343
+ # ───────── Result rendering helper ─────────
344
+ def render_result(tool: str, raw: dict[str, Any]) -> tuple[str, str, str, str, str, list[list[str]]]:
345
+ text = _extract_tool_text(raw)
346
+ is_error = _extract_tool_error(raw) or text.startswith("ERROR:") or text.startswith("Error:")
347
+ badge = "❌ error" if is_error else "βœ… ok"
348
+ status_line = f"**{tool}** β€” {badge}"
349
+ state = state_payload()
350
+ return (
351
+ status_line, # output_status
352
+ text, # output_view
353
+ json.dumps(raw, indent=2), # raw_response
354
+ _format_status(state), # state_summary (top + summary panel β€” same content)
355
+ json.dumps(state, indent=2, default=str), # state_json
356
+ _format_history(state), # history_table
357
+ )
358
 
359
+ # ───────── Session handlers ─────────
360
  async def on_reset(setup_text: str, verify_text: str):
361
+ raw = await web_manager.reset_environment(
362
  {"setup": _lines(setup_text), "verify": _lines(verify_text)}
363
  )
364
  state = state_payload()
365
+ obs = raw.get("observation", {}) if isinstance(raw, dict) else {}
366
+ meta = obs.get("metadata", {}) if isinstance(obs, dict) else {}
367
+ err = meta.get("error") if isinstance(meta, dict) else None
368
+ sandbox_id = state.get("sandbox_id") or meta.get("sandbox_id") or "β€”"
369
+ if err:
370
+ status_line = f"**reset** β€” ❌ {err}"
371
+ text = err
372
+ else:
373
+ status_line = f"**reset** β€” βœ… sandbox `{sandbox_id}` ready"
374
+ text = f"Sandbox ready: {sandbox_id}"
375
+ return (
376
+ _format_status(state),
377
+ status_line,
378
+ text,
379
+ json.dumps(raw, indent=2),
380
+ _format_status(state),
381
+ json.dumps(state, indent=2, default=str),
382
+ _format_history(state),
383
+ )
384
 
385
  async def on_close():
386
  await web_manager._run_sync_in_thread_pool(web_manager.env.close)
387
+ return (
388
+ _format_status({}),
389
+ "**close** β€” πŸ›‘ session closed",
390
+ "Session closed.",
391
+ "{}",
392
+ _format_status({}),
393
+ "{}",
394
+ [],
 
 
 
 
 
 
395
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
+ # ───────── Universal run handler ─────────
398
+ async def on_run(
399
+ tool: str,
400
+ # bash
401
+ bash_command: str, bash_timeout: float,
402
+ # read
403
+ read_path: str, read_offset: float | None, read_limit: float | None,
404
+ # write
405
+ write_path: str, write_content: str,
406
+ # edit
407
+ edit_path: str, edit_old: str, edit_new: str, edit_replace_all: bool,
408
+ # multi_edit
409
+ multi_edit_path: str, multi_edit_json: str,
410
+ # glob
411
+ glob_pattern: str, glob_path: str,
412
+ # grep
413
+ grep_pattern: str, grep_path: str, grep_include: str,
414
+ # ls
415
+ ls_path: str, ls_ignore: str,
416
+ # todo_write
417
+ todo_json: str,
418
+ ):
419
+ try:
420
+ if tool == "bash":
421
+ args = {
422
+ "command": bash_command,
423
+ "timeout": float(bash_timeout) if bash_timeout else 30,
424
+ }
425
+ elif tool == "read":
426
+ args = {"file_path": read_path}
427
+ if read_offset not in (None, "", 0):
428
+ args["offset"] = int(read_offset)
429
+ if read_limit not in (None, ""):
430
+ args["limit"] = int(read_limit)
431
+ elif tool == "write":
432
+ args = {"file_path": write_path, "content": write_content}
433
+ elif tool == "edit":
434
+ args = {
435
+ "file_path": edit_path,
436
+ "old_string": edit_old,
437
+ "new_string": edit_new,
438
+ "replace_all": bool(edit_replace_all),
439
+ }
440
+ elif tool == "multi_edit":
441
+ args = {
442
+ "file_path": multi_edit_path,
443
+ "edits": json.loads(multi_edit_json),
444
+ }
445
+ elif tool == "glob":
446
+ args = {"pattern": glob_pattern}
447
+ if glob_path.strip():
448
+ args["path"] = glob_path
449
+ elif tool == "grep":
450
+ args = {"pattern": grep_pattern}
451
+ if grep_path.strip():
452
+ args["path"] = grep_path
453
+ if grep_include.strip():
454
+ args["include"] = grep_include
455
+ elif tool == "ls":
456
+ args = {"path": ls_path or "."}
457
+ ignore = _csv_list(ls_ignore)
458
+ if ignore:
459
+ args["ignore"] = ignore
460
+ elif tool == "todo_write":
461
+ args = {"todos": json.loads(todo_json)}
462
+ elif tool == "submit_solution":
463
+ args = {}
464
+ else:
465
+ return (
466
+ _format_status(state_payload()),
467
+ f"**{tool}** β€” ❌ unknown tool",
468
+ f"unknown tool: {tool}",
469
+ "{}",
470
+ _format_status(state_payload()),
471
+ "{}",
472
+ [],
473
+ )
474
+ except (json.JSONDecodeError, ValueError) as exc:
475
+ state = state_payload()
476
+ return (
477
+ _format_status(state),
478
+ f"**{tool}** β€” ❌ bad input: {exc}",
479
+ f"Input parse error: {exc}",
480
+ "{}",
481
+ _format_status(state),
482
+ json.dumps(state, indent=2, default=str),
483
+ _format_history(state),
484
+ )
485
 
486
+ raw = await web_manager.step_environment(
487
+ {"tool_name": tool, "arguments": args}
 
 
488
  )
489
+ state = state_payload()
490
+ rendered = render_result(tool, raw)
491
+ return (_format_status(state), *rendered)
492
 
493
+ # ───────── Wire up events ─────────
494
+ all_inputs = [
495
+ tool_dropdown,
496
+ bash_command, bash_timeout,
497
+ read_path, read_offset, read_limit,
498
+ write_path, write_content,
499
+ edit_path, edit_old, edit_new, edit_replace_all,
500
+ multi_edit_path, multi_edit_json,
501
+ glob_pattern, glob_path,
502
+ grep_pattern, grep_path, grep_include,
503
+ ls_path, ls_ignore,
504
+ todo_json,
505
+ ]
506
+ all_outputs = [
507
+ status_md,
508
+ output_status,
509
+ output_view,
510
+ raw_response,
511
+ state_summary,
512
+ state_json,
513
+ history_table,
514
+ ]
515
 
516
+ run_btn.click(on_run, inputs=all_inputs, outputs=all_outputs)
517
+ reset_btn.click(
518
+ on_reset, inputs=[setup_input, verify_input], outputs=all_outputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  )
520
+ close_btn.click(on_close, outputs=all_outputs)
 
 
521
 
522
  return demo