fleet-python 0.2.66b2__py3-none-any.whl → 0.2.105__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.
- examples/export_tasks.py +16 -5
- examples/export_tasks_filtered.py +245 -0
- examples/fetch_tasks.py +230 -0
- examples/import_tasks.py +140 -8
- examples/iterate_verifiers.py +725 -0
- fleet/__init__.py +128 -5
- fleet/_async/__init__.py +27 -3
- fleet/_async/base.py +24 -9
- fleet/_async/client.py +938 -41
- fleet/_async/env/client.py +60 -3
- fleet/_async/instance/client.py +52 -7
- fleet/_async/models.py +15 -0
- fleet/_async/resources/api.py +200 -0
- fleet/_async/resources/sqlite.py +1801 -46
- fleet/_async/tasks.py +122 -25
- fleet/_async/verifiers/bundler.py +22 -21
- fleet/_async/verifiers/verifier.py +25 -19
- fleet/agent/__init__.py +32 -0
- fleet/agent/gemini_cua/Dockerfile +45 -0
- fleet/agent/gemini_cua/__init__.py +10 -0
- fleet/agent/gemini_cua/agent.py +759 -0
- fleet/agent/gemini_cua/mcp/main.py +108 -0
- fleet/agent/gemini_cua/mcp_server/__init__.py +5 -0
- fleet/agent/gemini_cua/mcp_server/main.py +105 -0
- fleet/agent/gemini_cua/mcp_server/tools.py +178 -0
- fleet/agent/gemini_cua/requirements.txt +5 -0
- fleet/agent/gemini_cua/start.sh +30 -0
- fleet/agent/orchestrator.py +854 -0
- fleet/agent/types.py +49 -0
- fleet/agent/utils.py +34 -0
- fleet/base.py +34 -9
- fleet/cli.py +1061 -0
- fleet/client.py +1060 -48
- fleet/config.py +1 -1
- fleet/env/__init__.py +16 -0
- fleet/env/client.py +60 -3
- fleet/eval/__init__.py +15 -0
- fleet/eval/uploader.py +231 -0
- fleet/exceptions.py +8 -0
- fleet/instance/client.py +53 -8
- fleet/instance/models.py +1 -0
- fleet/models.py +303 -0
- fleet/proxy/__init__.py +25 -0
- fleet/proxy/proxy.py +453 -0
- fleet/proxy/whitelist.py +244 -0
- fleet/resources/api.py +200 -0
- fleet/resources/sqlite.py +1845 -46
- fleet/tasks.py +113 -20
- fleet/utils/__init__.py +7 -0
- fleet/utils/http_logging.py +178 -0
- fleet/utils/logging.py +13 -0
- fleet/utils/playwright.py +440 -0
- fleet/verifiers/bundler.py +22 -21
- fleet/verifiers/db.py +985 -1
- fleet/verifiers/decorator.py +1 -1
- fleet/verifiers/verifier.py +25 -19
- {fleet_python-0.2.66b2.dist-info → fleet_python-0.2.105.dist-info}/METADATA +28 -1
- fleet_python-0.2.105.dist-info/RECORD +115 -0
- {fleet_python-0.2.66b2.dist-info → fleet_python-0.2.105.dist-info}/WHEEL +1 -1
- fleet_python-0.2.105.dist-info/entry_points.txt +2 -0
- tests/test_app_method.py +85 -0
- tests/test_expect_exactly.py +4148 -0
- tests/test_expect_only.py +2593 -0
- tests/test_instance_dispatch.py +607 -0
- tests/test_sqlite_resource_dual_mode.py +263 -0
- tests/test_sqlite_shared_memory_behavior.py +117 -0
- fleet_python-0.2.66b2.dist-info/RECORD +0 -81
- tests/test_verifier_security.py +0 -427
- {fleet_python-0.2.66b2.dist-info → fleet_python-0.2.105.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.66b2.dist-info → fleet_python-0.2.105.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"""Playwright browser control utilities.
|
|
2
|
+
|
|
3
|
+
Provides PlaywrightComputer class for browser automation with:
|
|
4
|
+
- Mouse actions (click, move, drag, scroll)
|
|
5
|
+
- Keyboard actions (type, key combinations)
|
|
6
|
+
- Screenshot capture
|
|
7
|
+
- Normalized coordinate support (0-1000 range)
|
|
8
|
+
|
|
9
|
+
Key mapping follows the action spec convention for cross-platform compatibility.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import logging
|
|
14
|
+
from typing import List, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from playwright.async_api import async_playwright, Page, Browser, BrowserContext
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# =============================================================================
|
|
22
|
+
# Key Mapping - Action spec keys to Playwright keys
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
PLAYWRIGHT_KEY_MAP = {
|
|
26
|
+
# Common keys
|
|
27
|
+
"enter": "Enter", "return": "Enter", "tab": "Tab",
|
|
28
|
+
"escape": "Escape", "esc": "Escape", "space": " ",
|
|
29
|
+
"backspace": "Backspace", "delete": "Delete", "insert": "Insert",
|
|
30
|
+
|
|
31
|
+
# Modifiers
|
|
32
|
+
"alt": "Alt", "alt_left": "Alt", "alt_right": "Alt",
|
|
33
|
+
"control": "Control", "control_left": "Control", "control_right": "Control",
|
|
34
|
+
"ctrl": "Control", "ctrl_left": "Control", "ctrl_right": "Control",
|
|
35
|
+
"shift": "Shift", "shift_left": "Shift", "shift_right": "Shift",
|
|
36
|
+
"caps_lock": "CapsLock", "capslock": "CapsLock",
|
|
37
|
+
"meta": "Meta", "meta_left": "Meta", "meta_right": "Meta",
|
|
38
|
+
"command": "Meta", "cmd": "Meta", "super": "Meta", "win": "Meta", "windows": "Meta",
|
|
39
|
+
"num_lock": "NumLock", "numlock": "NumLock",
|
|
40
|
+
"scroll_lock": "ScrollLock", "scrolllock": "ScrollLock",
|
|
41
|
+
|
|
42
|
+
# Navigation
|
|
43
|
+
"arrow_down": "ArrowDown", "arrow_up": "ArrowUp",
|
|
44
|
+
"arrow_left": "ArrowLeft", "arrow_right": "ArrowRight",
|
|
45
|
+
"down": "ArrowDown", "up": "ArrowUp", "left": "ArrowLeft", "right": "ArrowRight",
|
|
46
|
+
"end": "End", "home": "Home",
|
|
47
|
+
"page_down": "PageDown", "pagedown": "PageDown",
|
|
48
|
+
"page_up": "PageUp", "pageup": "PageUp",
|
|
49
|
+
|
|
50
|
+
# Function keys
|
|
51
|
+
**{f"f{i}": f"F{i}" for i in range(1, 21)},
|
|
52
|
+
|
|
53
|
+
# Symbols
|
|
54
|
+
"backquote": "`", "grave": "`", "tilde": "`",
|
|
55
|
+
"backslash": "\\", "bracket_left": "[", "bracketleft": "[",
|
|
56
|
+
"bracket_right": "]", "bracketright": "]",
|
|
57
|
+
"comma": ",", "double_quote": '"', "doublequote": '"',
|
|
58
|
+
"equal": "=", "equals": "=", "minus": "-", "dash": "-",
|
|
59
|
+
"period": ".", "dot": ".", "quote": "'", "apostrophe": "'",
|
|
60
|
+
"semicolon": ";", "slash": "/", "forward_slash": "/", "forwardslash": "/",
|
|
61
|
+
|
|
62
|
+
# Numpad
|
|
63
|
+
**{f"numpad_{i}": f"Numpad{i}" for i in range(10)},
|
|
64
|
+
**{f"numpad{i}": f"Numpad{i}" for i in range(10)},
|
|
65
|
+
"numpad_add": "NumpadAdd", "numpadadd": "NumpadAdd",
|
|
66
|
+
"numpad_subtract": "NumpadSubtract", "numpadsubtract": "NumpadSubtract",
|
|
67
|
+
"numpad_multiply": "NumpadMultiply", "numpadmultiply": "NumpadMultiply",
|
|
68
|
+
"numpad_divide": "NumpadDivide", "numpaddivide": "NumpadDivide",
|
|
69
|
+
"numpad_decimal": "NumpadDecimal", "numpaddecimal": "NumpadDecimal",
|
|
70
|
+
"numpad_enter": "NumpadEnter", "numpadenter": "NumpadEnter",
|
|
71
|
+
|
|
72
|
+
# Media
|
|
73
|
+
"audio_volume_mute": "AudioVolumeMute",
|
|
74
|
+
"audio_volume_down": "AudioVolumeDown",
|
|
75
|
+
"audio_volume_up": "AudioVolumeUp",
|
|
76
|
+
"media_track_next": "MediaTrackNext",
|
|
77
|
+
"media_track_previous": "MediaTrackPrevious",
|
|
78
|
+
"media_stop": "MediaStop",
|
|
79
|
+
"media_play_pause": "MediaPlayPause",
|
|
80
|
+
|
|
81
|
+
# Other
|
|
82
|
+
"print_screen": "PrintScreen", "printscreen": "PrintScreen",
|
|
83
|
+
"pause": "Pause", "context_menu": "ContextMenu", "contextmenu": "ContextMenu",
|
|
84
|
+
"help": "Help",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
MODIFIER_KEYS = {
|
|
88
|
+
"Alt", "Control", "Shift", "Meta",
|
|
89
|
+
"alt", "alt_left", "alt_right",
|
|
90
|
+
"control", "control_left", "control_right", "ctrl", "ctrl_left", "ctrl_right",
|
|
91
|
+
"shift", "shift_left", "shift_right",
|
|
92
|
+
"meta", "meta_left", "meta_right", "command", "cmd", "super", "win", "windows",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Key specification for tool docstrings
|
|
96
|
+
KEY_SPEC = (
|
|
97
|
+
"Key specification: * Common: enter, tab, escape, space, backspace, delete "
|
|
98
|
+
"* Modifiers: alt_left, control_left, control_right, shift_left, caps_lock, meta "
|
|
99
|
+
"* Navigation: arrow_down, arrow_right, end, home, page_down "
|
|
100
|
+
"* Function: f1 to f12 "
|
|
101
|
+
"* Alphanumeric: key_a to key_z, digit_0 to digit_9 "
|
|
102
|
+
"* Symbols: backquote, backslash, bracket_left, bracket_right, comma, double_quote, "
|
|
103
|
+
"equal, minus, period, quote, semicolon, slash "
|
|
104
|
+
"* Numpad: numpad_0 to numpad_9, numpad_add, numpad_divide, numpad_enter, numpad_multiply"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def map_key(key: str) -> str:
|
|
109
|
+
"""Map action spec key name to Playwright key name.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
key: Key name in action spec format (e.g., "key_a", "control_left")
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Playwright key name (e.g., "a", "Control")
|
|
116
|
+
"""
|
|
117
|
+
k = key.lower().strip()
|
|
118
|
+
if k in PLAYWRIGHT_KEY_MAP:
|
|
119
|
+
return PLAYWRIGHT_KEY_MAP[k]
|
|
120
|
+
if k.startswith("key_") and len(k) == 5:
|
|
121
|
+
return k[4].lower()
|
|
122
|
+
if k.startswith("digit_") and len(k) == 7:
|
|
123
|
+
return k[6]
|
|
124
|
+
if len(key) == 1:
|
|
125
|
+
return key
|
|
126
|
+
return key
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_modifier(key: str) -> bool:
|
|
130
|
+
"""Check if a key is a modifier key.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
key: Key name to check
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if the key is a modifier (Alt, Control, Shift, Meta)
|
|
137
|
+
"""
|
|
138
|
+
return key.lower().strip() in MODIFIER_KEYS or map_key(key) in {"Alt", "Control", "Shift", "Meta"}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# =============================================================================
|
|
142
|
+
# PlaywrightComputer - Browser control
|
|
143
|
+
# =============================================================================
|
|
144
|
+
|
|
145
|
+
class PlaywrightComputer:
|
|
146
|
+
"""Browser control via Playwright.
|
|
147
|
+
|
|
148
|
+
Provides a high-level interface for browser automation:
|
|
149
|
+
- Mouse actions with optional visual highlighting
|
|
150
|
+
- Keyboard input with proper modifier handling
|
|
151
|
+
- Screenshot capture
|
|
152
|
+
- Automatic page load waiting
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
screen_size: Tuple of (width, height) for viewport
|
|
156
|
+
initial_url: URL to navigate to on start
|
|
157
|
+
headless: Run browser without visible window
|
|
158
|
+
highlight_mouse: Show visual indicator for mouse actions (useful for debugging)
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
computer = PlaywrightComputer(
|
|
162
|
+
screen_size=(1366, 768),
|
|
163
|
+
initial_url="https://example.com",
|
|
164
|
+
headless=False,
|
|
165
|
+
highlight_mouse=True,
|
|
166
|
+
)
|
|
167
|
+
await computer.start()
|
|
168
|
+
await computer.mouse_click(683, 384) # Click center
|
|
169
|
+
screenshot = await computer.screenshot()
|
|
170
|
+
await computer.stop()
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
screen_size: Tuple[int, int],
|
|
176
|
+
initial_url: str,
|
|
177
|
+
headless: bool = True,
|
|
178
|
+
highlight_mouse: bool = False,
|
|
179
|
+
):
|
|
180
|
+
self._screen_size = screen_size
|
|
181
|
+
self._initial_url = initial_url
|
|
182
|
+
self._headless = headless
|
|
183
|
+
self._highlight_mouse = highlight_mouse
|
|
184
|
+
self._playwright = None
|
|
185
|
+
self._browser: Optional[Browser] = None
|
|
186
|
+
self._context: Optional[BrowserContext] = None
|
|
187
|
+
self._page: Optional[Page] = None
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def width(self) -> int:
|
|
191
|
+
"""Viewport width in pixels."""
|
|
192
|
+
return self._screen_size[0]
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def height(self) -> int:
|
|
196
|
+
"""Viewport height in pixels."""
|
|
197
|
+
return self._screen_size[1]
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def current_url(self) -> str:
|
|
201
|
+
"""Current page URL."""
|
|
202
|
+
return self._page.url if self._page else ""
|
|
203
|
+
|
|
204
|
+
async def _handle_new_page(self, new_page: Page):
|
|
205
|
+
"""Handle new tab by redirecting to current page."""
|
|
206
|
+
new_url = new_page.url
|
|
207
|
+
await new_page.close()
|
|
208
|
+
await self._page.goto(new_url)
|
|
209
|
+
|
|
210
|
+
async def start(self):
|
|
211
|
+
"""Start the browser and navigate to initial URL."""
|
|
212
|
+
logger.info(f"Starting browser (headless={self._headless})...")
|
|
213
|
+
self._playwright = await async_playwright().start()
|
|
214
|
+
self._browser = await self._playwright.chromium.launch(
|
|
215
|
+
headless=self._headless,
|
|
216
|
+
args=[
|
|
217
|
+
"--no-sandbox",
|
|
218
|
+
"--disable-extensions",
|
|
219
|
+
"--disable-file-system",
|
|
220
|
+
"--disable-plugins",
|
|
221
|
+
"--disable-dev-shm-usage",
|
|
222
|
+
"--disable-background-networking",
|
|
223
|
+
"--disable-default-apps",
|
|
224
|
+
"--disable-sync",
|
|
225
|
+
],
|
|
226
|
+
)
|
|
227
|
+
self._context = await self._browser.new_context(
|
|
228
|
+
viewport={"width": self._screen_size[0], "height": self._screen_size[1]}
|
|
229
|
+
)
|
|
230
|
+
self._page = await self._context.new_page()
|
|
231
|
+
self._context.on("page", self._handle_new_page)
|
|
232
|
+
await self._page.goto(self._initial_url)
|
|
233
|
+
await self._page.wait_for_load_state()
|
|
234
|
+
logger.info(f"Browser ready: {self._initial_url}")
|
|
235
|
+
|
|
236
|
+
async def stop(self):
|
|
237
|
+
"""Stop the browser and clean up resources."""
|
|
238
|
+
if self._context:
|
|
239
|
+
await self._context.close()
|
|
240
|
+
if self._browser:
|
|
241
|
+
try:
|
|
242
|
+
await self._browser.close()
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
if self._playwright:
|
|
246
|
+
await self._playwright.stop()
|
|
247
|
+
logger.info("Browser stopped")
|
|
248
|
+
|
|
249
|
+
async def screenshot(self) -> bytes:
|
|
250
|
+
"""Take a screenshot of the current viewport.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
PNG image data as bytes
|
|
254
|
+
"""
|
|
255
|
+
await self._page.wait_for_load_state()
|
|
256
|
+
await asyncio.sleep(0.5)
|
|
257
|
+
return await self._page.screenshot(type="png", full_page=False)
|
|
258
|
+
|
|
259
|
+
async def _highlight(self, x: int, y: int):
|
|
260
|
+
"""Show visual highlight at mouse position (for debugging)."""
|
|
261
|
+
if not self._highlight_mouse:
|
|
262
|
+
return
|
|
263
|
+
await self._page.evaluate(f"""
|
|
264
|
+
() => {{
|
|
265
|
+
const div = document.createElement('div');
|
|
266
|
+
div.style.cssText = 'position:fixed;width:20px;height:20px;border-radius:50%;border:4px solid red;pointer-events:none;z-index:9999;left:{x-10}px;top:{y-10}px;';
|
|
267
|
+
document.body.appendChild(div);
|
|
268
|
+
setTimeout(() => div.remove(), 2000);
|
|
269
|
+
}}
|
|
270
|
+
""")
|
|
271
|
+
await asyncio.sleep(1)
|
|
272
|
+
|
|
273
|
+
# -------------------------------------------------------------------------
|
|
274
|
+
# Mouse actions
|
|
275
|
+
# -------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
async def mouse_click(self, x: int, y: int, button: str = "left", repeats: int = 1) -> None:
|
|
278
|
+
"""Click at position.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
x: X coordinate in pixels
|
|
282
|
+
y: Y coordinate in pixels
|
|
283
|
+
button: Mouse button ('left', 'middle', 'right')
|
|
284
|
+
repeats: Number of clicks (2 for double-click)
|
|
285
|
+
"""
|
|
286
|
+
await self._highlight(x, y)
|
|
287
|
+
for _ in range(repeats):
|
|
288
|
+
await self._page.mouse.click(x, y, button=button)
|
|
289
|
+
await self._page.wait_for_load_state()
|
|
290
|
+
|
|
291
|
+
async def mouse_move(self, x: int, y: int) -> None:
|
|
292
|
+
"""Move mouse to position.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
x: X coordinate in pixels
|
|
296
|
+
y: Y coordinate in pixels
|
|
297
|
+
"""
|
|
298
|
+
await self._highlight(x, y)
|
|
299
|
+
await self._page.mouse.move(x, y)
|
|
300
|
+
await self._page.wait_for_load_state()
|
|
301
|
+
|
|
302
|
+
async def mouse_down(self, button: str = "left") -> None:
|
|
303
|
+
"""Press mouse button down.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
button: Mouse button ('left', 'middle', 'right')
|
|
307
|
+
"""
|
|
308
|
+
await self._page.mouse.down(button=button)
|
|
309
|
+
|
|
310
|
+
async def mouse_up(self, button: str = "left") -> None:
|
|
311
|
+
"""Release mouse button.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
button: Mouse button ('left', 'middle', 'right')
|
|
315
|
+
"""
|
|
316
|
+
await self._page.mouse.up(button=button)
|
|
317
|
+
await self._page.wait_for_load_state()
|
|
318
|
+
|
|
319
|
+
async def mouse_scroll(self, dx: int, dy: int) -> None:
|
|
320
|
+
"""Scroll the mouse wheel.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
dx: Horizontal scroll amount in pixels
|
|
324
|
+
dy: Vertical scroll amount in pixels
|
|
325
|
+
"""
|
|
326
|
+
await self._page.mouse.wheel(dx, dy)
|
|
327
|
+
await self._page.wait_for_load_state()
|
|
328
|
+
|
|
329
|
+
async def mouse_drag(
|
|
330
|
+
self,
|
|
331
|
+
x_start: int,
|
|
332
|
+
y_start: int,
|
|
333
|
+
x_end: int,
|
|
334
|
+
y_end: int,
|
|
335
|
+
button: str = "left",
|
|
336
|
+
) -> None:
|
|
337
|
+
"""Drag from one position to another.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
x_start: Starting X coordinate
|
|
341
|
+
y_start: Starting Y coordinate
|
|
342
|
+
x_end: Ending X coordinate
|
|
343
|
+
y_end: Ending Y coordinate
|
|
344
|
+
button: Mouse button to hold during drag
|
|
345
|
+
"""
|
|
346
|
+
await self._highlight(x_start, y_start)
|
|
347
|
+
await self._page.mouse.move(x_start, y_start)
|
|
348
|
+
await self._page.mouse.down(button=button)
|
|
349
|
+
await self._highlight(x_end, y_end)
|
|
350
|
+
await self._page.mouse.move(x_end, y_end)
|
|
351
|
+
await self._page.mouse.up(button=button)
|
|
352
|
+
await self._page.wait_for_load_state()
|
|
353
|
+
|
|
354
|
+
# -------------------------------------------------------------------------
|
|
355
|
+
# Keyboard actions
|
|
356
|
+
# -------------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
async def type_text(self, text: str, press_enter: bool = False) -> None:
|
|
359
|
+
"""Type text using the keyboard.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
text: Text to type
|
|
363
|
+
press_enter: Whether to press Enter after typing
|
|
364
|
+
"""
|
|
365
|
+
await self._page.keyboard.type(text)
|
|
366
|
+
await self._page.wait_for_load_state()
|
|
367
|
+
if press_enter:
|
|
368
|
+
await self._page.keyboard.press("Enter")
|
|
369
|
+
await self._page.wait_for_load_state()
|
|
370
|
+
|
|
371
|
+
async def key_combination(self, keys: List[str]) -> None:
|
|
372
|
+
"""Press a key combination (e.g., Ctrl+C).
|
|
373
|
+
|
|
374
|
+
Handles modifiers properly - holds them down while pressing other keys.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
keys: List of keys to press together
|
|
378
|
+
"""
|
|
379
|
+
if not keys:
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
modifiers = [map_key(k) for k in keys if is_modifier(k)]
|
|
383
|
+
regular = [map_key(k) for k in keys if not is_modifier(k)]
|
|
384
|
+
|
|
385
|
+
# Press modifiers down
|
|
386
|
+
for mod in modifiers:
|
|
387
|
+
await self._page.keyboard.down(mod)
|
|
388
|
+
|
|
389
|
+
# Press regular keys
|
|
390
|
+
for key in regular:
|
|
391
|
+
await self._page.keyboard.press(key)
|
|
392
|
+
|
|
393
|
+
# If only modifiers, brief pause
|
|
394
|
+
if not regular and modifiers:
|
|
395
|
+
await asyncio.sleep(0.05)
|
|
396
|
+
|
|
397
|
+
# Release modifiers
|
|
398
|
+
for mod in reversed(modifiers):
|
|
399
|
+
await self._page.keyboard.up(mod)
|
|
400
|
+
|
|
401
|
+
await self._page.wait_for_load_state()
|
|
402
|
+
|
|
403
|
+
async def key_down(self, key: str) -> None:
|
|
404
|
+
"""Press a key down (without releasing).
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
key: Key to press down
|
|
408
|
+
"""
|
|
409
|
+
await self._page.keyboard.down(map_key(key))
|
|
410
|
+
|
|
411
|
+
async def key_up(self, key: str) -> None:
|
|
412
|
+
"""Release a key.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
key: Key to release
|
|
416
|
+
"""
|
|
417
|
+
await self._page.keyboard.up(map_key(key))
|
|
418
|
+
await self._page.wait_for_load_state()
|
|
419
|
+
|
|
420
|
+
# -------------------------------------------------------------------------
|
|
421
|
+
# Utilities
|
|
422
|
+
# -------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
async def wait(self, seconds: int) -> None:
|
|
425
|
+
"""Wait for a number of seconds.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
seconds: Number of seconds to wait
|
|
429
|
+
"""
|
|
430
|
+
await asyncio.sleep(seconds)
|
|
431
|
+
|
|
432
|
+
async def goto(self, url: str) -> None:
|
|
433
|
+
"""Navigate to a URL.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
url: URL to navigate to
|
|
437
|
+
"""
|
|
438
|
+
await self._page.goto(url)
|
|
439
|
+
await self._page.wait_for_load_state()
|
|
440
|
+
|
fleet/verifiers/bundler.py
CHANGED
|
@@ -37,7 +37,7 @@ class FunctionBundler:
|
|
|
37
37
|
) -> bytes:
|
|
38
38
|
"""Create a function bundle with statically extracted code."""
|
|
39
39
|
|
|
40
|
-
logger.info(f"Creating function bundle for {func.__name__}")
|
|
40
|
+
# logger.info(f"Creating function bundle for {func.__name__}")
|
|
41
41
|
|
|
42
42
|
# 1. Parse the main function and find dependencies
|
|
43
43
|
mod_file = Path(func.__code__.co_filename)
|
|
@@ -115,7 +115,7 @@ class FunctionBundler:
|
|
|
115
115
|
|
|
116
116
|
# Find function calls within the verifier function
|
|
117
117
|
called_functions = self._extract_function_calls(main_func_ast)
|
|
118
|
-
logger.debug(f"Functions called in verifier: {called_functions}")
|
|
118
|
+
# logger.debug(f"Functions called in verifier: {called_functions}")
|
|
119
119
|
|
|
120
120
|
# Find all functions defined in the module
|
|
121
121
|
module_functions = {}
|
|
@@ -128,7 +128,7 @@ class FunctionBundler:
|
|
|
128
128
|
for func_name in called_functions:
|
|
129
129
|
if func_name in module_functions and func_name != func.__name__:
|
|
130
130
|
same_module_deps.append(func_name)
|
|
131
|
-
logger.debug(f"Found same-module dependency: {func_name}")
|
|
131
|
+
# logger.debug(f"Found same-module dependency: {func_name}")
|
|
132
132
|
|
|
133
133
|
# Separate local and external imports
|
|
134
134
|
local_imports = {}
|
|
@@ -292,7 +292,7 @@ class FunctionBundler:
|
|
|
292
292
|
code = ast.unparse(node)
|
|
293
293
|
extracted_code.append(code)
|
|
294
294
|
except Exception as e:
|
|
295
|
-
logger.warning(f"Could not unparse AST node: {e}")
|
|
295
|
+
# logger.warning(f"Could not unparse AST node: {e}")
|
|
296
296
|
# Fallback to original source extraction
|
|
297
297
|
lines = content.split("\n")
|
|
298
298
|
start_line = node.lineno - 1
|
|
@@ -305,11 +305,11 @@ class FunctionBundler:
|
|
|
305
305
|
extracted_code.append(code)
|
|
306
306
|
|
|
307
307
|
result = "\n\n".join(extracted_code)
|
|
308
|
-
logger.debug(f"Extracted {len(extracted_code)} items from {file_path}")
|
|
308
|
+
# logger.debug(f"Extracted {len(extracted_code)} items from {file_path}")
|
|
309
309
|
return result
|
|
310
310
|
|
|
311
311
|
except Exception as e:
|
|
312
|
-
logger.warning(f"Failed to extract functions from {file_path}: {e}")
|
|
312
|
+
# logger.warning(f"Failed to extract functions from {file_path}: {e}")
|
|
313
313
|
# Fallback to including the entire file
|
|
314
314
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
315
315
|
return f.read()
|
|
@@ -464,14 +464,14 @@ class FunctionBundler:
|
|
|
464
464
|
version = dist.version # Get the installed version
|
|
465
465
|
package_with_version = f"{package_name}=={version}"
|
|
466
466
|
packages.add(package_with_version)
|
|
467
|
-
logger.debug(f"Mapped {mod} -> {package_with_version}")
|
|
467
|
+
# logger.debug(f"Mapped {mod} -> {package_with_version}")
|
|
468
468
|
except imd.PackageNotFoundError:
|
|
469
469
|
# Skip stdlib or local modules
|
|
470
|
-
logger.debug(f"Skipping {mod} (stdlib or local)")
|
|
470
|
+
# logger.debug(f"Skipping {mod} (stdlib or local)")
|
|
471
471
|
continue
|
|
472
472
|
|
|
473
473
|
package_list = list(packages)
|
|
474
|
-
logger.debug(f"Final package list: {package_list}")
|
|
474
|
+
# logger.debug(f"Final package list: {package_list}")
|
|
475
475
|
return package_list
|
|
476
476
|
|
|
477
477
|
def _merge_requirements(
|
|
@@ -511,10 +511,10 @@ class FunctionBundler:
|
|
|
511
511
|
if pkg_name not in seen_packages:
|
|
512
512
|
final_requirements.append(req)
|
|
513
513
|
seen_packages.add(pkg_name)
|
|
514
|
-
else:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
# else:
|
|
515
|
+
# logger.debug(
|
|
516
|
+
# f"Skipping auto-detected {req}, using explicit version instead"
|
|
517
|
+
# )
|
|
518
518
|
|
|
519
519
|
# Always ensure fleet-python is included
|
|
520
520
|
if "fleet-python" not in seen_packages:
|
|
@@ -565,9 +565,9 @@ class FunctionBundler:
|
|
|
565
565
|
)
|
|
566
566
|
if dep_src:
|
|
567
567
|
same_module_code += f"\n{dep_src}\n"
|
|
568
|
-
logger.debug(
|
|
569
|
-
|
|
570
|
-
)
|
|
568
|
+
# logger.debug(
|
|
569
|
+
# f"Extracted same-module dependency: {dep_name}"
|
|
570
|
+
# )
|
|
571
571
|
|
|
572
572
|
# Create verifier.py with the main function
|
|
573
573
|
verifier_file = build_dir / "verifier.py"
|
|
@@ -586,7 +586,7 @@ class FunctionBundler:
|
|
|
586
586
|
{code}
|
|
587
587
|
"""
|
|
588
588
|
dest_path.write_text(extracted_content)
|
|
589
|
-
logger.debug(f"Created extracted file: {relative_path}")
|
|
589
|
+
# logger.debug(f"Created extracted file: {relative_path}")
|
|
590
590
|
|
|
591
591
|
# Ensure __init__.py files exist
|
|
592
592
|
self._ensure_init_files(Path(relative_path), build_dir)
|
|
@@ -595,7 +595,7 @@ class FunctionBundler:
|
|
|
595
595
|
return self._create_zip_bundle(build_dir)
|
|
596
596
|
|
|
597
597
|
except Exception as e:
|
|
598
|
-
logger.error(f"Failed to build function bundle: {e}")
|
|
598
|
+
# logger.error(f"Failed to build function bundle: {e}")
|
|
599
599
|
raise RuntimeError(f"Function bundle creation failed: {e}")
|
|
600
600
|
|
|
601
601
|
def _ensure_init_files(self, rel_path: Path, build_dir: Path):
|
|
@@ -607,7 +607,7 @@ class FunctionBundler:
|
|
|
607
607
|
if not init_file.exists():
|
|
608
608
|
init_file.parent.mkdir(parents=True, exist_ok=True)
|
|
609
609
|
init_file.write_text("# Auto-generated __init__.py")
|
|
610
|
-
logger.debug(f"Created __init__.py: {current}")
|
|
610
|
+
# logger.debug(f"Created __init__.py: {current}")
|
|
611
611
|
current = current.parent
|
|
612
612
|
|
|
613
613
|
def _create_zip_bundle(self, build_dir: Path) -> bytes:
|
|
@@ -621,7 +621,7 @@ class FunctionBundler:
|
|
|
621
621
|
zf.write(file_path, arcname)
|
|
622
622
|
|
|
623
623
|
bundle_size = len(zip_buffer.getvalue())
|
|
624
|
-
logger.debug(f"Created function bundle ({bundle_size:,} bytes)")
|
|
624
|
+
# logger.debug(f"Created function bundle ({bundle_size:,} bytes)")
|
|
625
625
|
return zip_buffer.getvalue()
|
|
626
626
|
|
|
627
627
|
def _extract_function_source(
|
|
@@ -662,7 +662,8 @@ class FunctionBundler:
|
|
|
662
662
|
return "\n".join(func_lines)
|
|
663
663
|
|
|
664
664
|
except Exception as e:
|
|
665
|
-
logger.warning(f"Failed to extract function {function_name}: {e}")
|
|
665
|
+
# logger.warning(f"Failed to extract function {function_name}: {e}")
|
|
666
|
+
pass
|
|
666
667
|
|
|
667
668
|
return None
|
|
668
669
|
|