use-computer 0.0.41__tar.gz → 0.0.43__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.
- {use_computer-0.0.41 → use_computer-0.0.43}/.env.example +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/PKG-INFO +25 -14
- {use_computer-0.0.41 → use_computer-0.0.43}/README.md +24 -13
- {use_computer-0.0.41 → use_computer-0.0.43}/pyproject.toml +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/scripts/smoke_wheel.py +3 -4
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/__init__.py +18 -7
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/__init__.py +2 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/agent.py +14 -6
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/final_state.py +1 -1
- use_computer-0.0.43/use_computer/agents/base/recordings.py +253 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/sandbox.py +1 -1
- use_computer-0.0.43/use_computer/agents/base/task_setup.py +277 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/core.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/actions.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/agent.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/agent.py +54 -4
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/tools.py +2 -1
- use_computer-0.0.43/use_computer/agents/prompts/osworld_pyautogui.txt +18 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/gemini.py +1 -1
- use_computer-0.0.43/use_computer/agents/providers/osworld.py +229 -0
- use_computer-0.0.43/use_computer/automation/__init__.py +28 -0
- use_computer-0.0.43/use_computer/automation/osworld.py +466 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/automation}/parsers.py +2 -2
- use_computer-0.0.43/use_computer/core/__init__.py +64 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/client.py +149 -29
- use_computer-0.0.43/use_computer/core/errors.py +103 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/sandbox.py +40 -10
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/harbor/agents.py +12 -0
- use_computer-0.0.43/use_computer/ios/__init__.py +33 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/apps.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/environment.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/input.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/keyboard.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/mouse.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/accessibility.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/display.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/recording.py +1 -1
- {use_computer-0.0.41/use_computer/harbor/environment → use_computer-0.0.43/use_computer}/setup_files.py +1 -1
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/__init__.py +3 -3
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/windows/shell.py +1 -1
- use_computer-0.0.41/use_computer/accessibility.py +0 -3
- use_computer-0.0.41/use_computer/agents/base/recordings.py +0 -62
- use_computer-0.0.41/use_computer/display.py +0 -3
- use_computer-0.0.41/use_computer/errors.py +0 -74
- use_computer-0.0.41/use_computer/harbor/environment/__init__.py +0 -17
- use_computer-0.0.41/use_computer/harbor/environment/gateway.py +0 -25
- use_computer-0.0.41/use_computer/harbor/environment/platforms/__init__.py +0 -216
- use_computer-0.0.41/use_computer/harbor/environment/platforms/ios.py +0 -105
- use_computer-0.0.41/use_computer/harbor/environment/platforms/macos.py +0 -293
- use_computer-0.0.41/use_computer/harbor/environment/platforms/ubuntu.py +0 -175
- use_computer-0.0.41/use_computer/harbor/environment/platforms/windows.py +0 -245
- use_computer-0.0.41/use_computer/harbor/environment/use_computer.py +0 -238
- use_computer-0.0.41/use_computer/harbor/environment/utils/__init__.py +0 -15
- use_computer-0.0.41/use_computer/harbor/environment/utils/verifier.py +0 -36
- use_computer-0.0.41/use_computer/ios/__init__.py +0 -14
- use_computer-0.0.41/use_computer/recording.py +0 -3
- use_computer-0.0.41/use_computer/screenshot.py +0 -3
- {use_computer-0.0.41 → use_computer-0.0.43}/.gitignore +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/actions.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/artifacts.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/desktop.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/prompts.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/screenshots.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/trajectory.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/README.md +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/models.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/anthropic.txt +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/gemini.txt +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/ios.txt +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/openai.txt +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/pyautogui.txt +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/anthropic.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/generic.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/openai.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/tinker_backend.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/types.py +0 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/automation}/ax_transpile.py +0 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/models.py +0 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/retry.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/harbor/__init__.py +0 -0
- {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/ios}/simulators.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/py.typed +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/screenshot.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/pre_command.ps1 +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ios.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu_check.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu_nograder.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows_check.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows_nograder.sh +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/windows/__init__.py +0 -0
- {use_computer-0.0.41 → use_computer-0.0.43}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.43
|
|
4
4
|
Summary: Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu
|
|
5
5
|
Project-URL: Homepage, https://use.computer
|
|
6
6
|
Project-URL: Documentation, https://api.use.computer/docs
|
|
@@ -38,31 +38,31 @@ Python client for [use.computer](https://use.computer) — rent dedicated VMs ac
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
pip install use-computer
|
|
41
|
-
export USE_COMPUTER_API_KEY=
|
|
41
|
+
export USE_COMPUTER_API_KEY=uc_live_...
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Optional agent and Harbor integrations are installed explicitly:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
pip install "use-computer[agents]" # computer-use agents and provider SDKs
|
|
48
|
-
pip install "use-computer[harbor]" # Harbor
|
|
49
|
-
pip install "use-computer[harbor,agents]" # Harbor
|
|
48
|
+
pip install "use-computer[harbor]" # Harbor agent wrapper dependencies
|
|
49
|
+
pip install "use-computer[harbor,agents]" # Harbor agent wrappers plus agents
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
Base installs only the SDK client and `httpx`. The `agents` extra installs the
|
|
53
|
-
agent runtime plus model-provider dependencies; `harbor` installs Harbor
|
|
54
|
-
|
|
53
|
+
agent runtime plus model-provider dependencies; `harbor` installs Harbor for
|
|
54
|
+
`use_computer.harbor.agents`.
|
|
55
55
|
|
|
56
56
|
```python
|
|
57
57
|
from use_computer.agents import AnthropicComputerAgent
|
|
58
|
-
from use_computer.harbor.environment import UseComputerEnvironment
|
|
59
58
|
```
|
|
60
59
|
|
|
61
|
-
In Harbor job YAML, use the SDK
|
|
60
|
+
In Harbor job YAML, use Harbor's built-in use.computer environment and the SDK
|
|
61
|
+
agent wrappers:
|
|
62
62
|
|
|
63
63
|
```yaml
|
|
64
64
|
environment:
|
|
65
|
-
|
|
65
|
+
type: use-computer
|
|
66
66
|
kwargs:
|
|
67
67
|
platform: macos
|
|
68
68
|
agents:
|
|
@@ -73,7 +73,10 @@ agents:
|
|
|
73
73
|
```python
|
|
74
74
|
from use_computer import Computer, SandboxType, SimulatorFamily
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
client = Computer()
|
|
77
|
+
reservation = client.reserve(hours=24)
|
|
78
|
+
|
|
79
|
+
with client.create(type="macos", reservation_id=reservation.id) as mac:
|
|
77
80
|
mac.exec_ssh("open -a TextEdit")
|
|
78
81
|
mac.keyboard.type("hello")
|
|
79
82
|
png = mac.screenshot.take_full_screen()
|
|
@@ -85,6 +88,10 @@ with Computer().create(type=SandboxType.IOS, family=SimulatorFamily.TV) as tv:
|
|
|
85
88
|
with Computer().create(type=SandboxType.IOS) as ios:
|
|
86
89
|
ios.input.long_press(120, 300, duration=1.0)
|
|
87
90
|
print(ios.exec("simctl getenv $UDID HOME").stdout) # CoreSimulator exec, not SSH
|
|
91
|
+
|
|
92
|
+
# Only needed when the same account key has more than one active Mac reservation.
|
|
93
|
+
with Computer().create(type="macos", reservation_id="...") as mac:
|
|
94
|
+
print(mac.exec_ssh("uname -a").stdout)
|
|
88
95
|
```
|
|
89
96
|
|
|
90
97
|
### Windows (Beta)
|
|
@@ -148,12 +155,16 @@ that snapshot version.
|
|
|
148
155
|
```python
|
|
149
156
|
client = Computer()
|
|
150
157
|
|
|
158
|
+
# Configure the desktop by hand over VNC, then snapshot it.
|
|
151
159
|
with client.create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
|
|
152
|
-
|
|
160
|
+
print("Set up the desktop here:", ubuntu.vnc_url)
|
|
161
|
+
# install apps, sign into accounts, pre-open the windows your agent starts from
|
|
162
|
+
input("Press Enter once the desktop is ready to snapshot...")
|
|
153
163
|
snapshot = ubuntu.snapshot("chrome-seeded-ubuntu")
|
|
154
164
|
|
|
165
|
+
# New sandboxes boot from that exact state, no setup needed.
|
|
155
166
|
with client.create(type="ubuntu", snapshot=snapshot.version) as seeded:
|
|
156
|
-
print(seeded.
|
|
167
|
+
print(seeded.vnc_url) # apps, logins, and open windows restored
|
|
157
168
|
```
|
|
158
169
|
|
|
159
170
|
Use `client.snapshots("ubuntu")` or `client.snapshots("windows")` to list saved
|
|
@@ -198,7 +209,7 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
198
209
|
| [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
|
|
199
210
|
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows desktop state |
|
|
200
211
|
|
|
201
|
-
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook).
|
|
212
|
+
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Harbor job YAMLs should use Harbor's `type: use-computer` environment plus `use_computer.harbor.agents:*` agent wrappers.
|
|
202
213
|
|
|
203
214
|
## Skill for AI coding assistants
|
|
204
215
|
|
|
@@ -206,4 +217,4 @@ Point your assistant at [`use-computer-cookbook/skills/SKILL.md`](https://github
|
|
|
206
217
|
|
|
207
218
|
## HTTP API
|
|
208
219
|
|
|
209
|
-
Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer
|
|
220
|
+
Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
|
|
@@ -4,31 +4,31 @@ Python client for [use.computer](https://use.computer) — rent dedicated VMs ac
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pip install use-computer
|
|
7
|
-
export USE_COMPUTER_API_KEY=
|
|
7
|
+
export USE_COMPUTER_API_KEY=uc_live_...
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
Optional agent and Harbor integrations are installed explicitly:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
pip install "use-computer[agents]" # computer-use agents and provider SDKs
|
|
14
|
-
pip install "use-computer[harbor]" # Harbor
|
|
15
|
-
pip install "use-computer[harbor,agents]" # Harbor
|
|
14
|
+
pip install "use-computer[harbor]" # Harbor agent wrapper dependencies
|
|
15
|
+
pip install "use-computer[harbor,agents]" # Harbor agent wrappers plus agents
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Base installs only the SDK client and `httpx`. The `agents` extra installs the
|
|
19
|
-
agent runtime plus model-provider dependencies; `harbor` installs Harbor
|
|
20
|
-
|
|
19
|
+
agent runtime plus model-provider dependencies; `harbor` installs Harbor for
|
|
20
|
+
`use_computer.harbor.agents`.
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
23
|
from use_computer.agents import AnthropicComputerAgent
|
|
24
|
-
from use_computer.harbor.environment import UseComputerEnvironment
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
In Harbor job YAML, use the SDK
|
|
26
|
+
In Harbor job YAML, use Harbor's built-in use.computer environment and the SDK
|
|
27
|
+
agent wrappers:
|
|
28
28
|
|
|
29
29
|
```yaml
|
|
30
30
|
environment:
|
|
31
|
-
|
|
31
|
+
type: use-computer
|
|
32
32
|
kwargs:
|
|
33
33
|
platform: macos
|
|
34
34
|
agents:
|
|
@@ -39,7 +39,10 @@ agents:
|
|
|
39
39
|
```python
|
|
40
40
|
from use_computer import Computer, SandboxType, SimulatorFamily
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
client = Computer()
|
|
43
|
+
reservation = client.reserve(hours=24)
|
|
44
|
+
|
|
45
|
+
with client.create(type="macos", reservation_id=reservation.id) as mac:
|
|
43
46
|
mac.exec_ssh("open -a TextEdit")
|
|
44
47
|
mac.keyboard.type("hello")
|
|
45
48
|
png = mac.screenshot.take_full_screen()
|
|
@@ -51,6 +54,10 @@ with Computer().create(type=SandboxType.IOS, family=SimulatorFamily.TV) as tv:
|
|
|
51
54
|
with Computer().create(type=SandboxType.IOS) as ios:
|
|
52
55
|
ios.input.long_press(120, 300, duration=1.0)
|
|
53
56
|
print(ios.exec("simctl getenv $UDID HOME").stdout) # CoreSimulator exec, not SSH
|
|
57
|
+
|
|
58
|
+
# Only needed when the same account key has more than one active Mac reservation.
|
|
59
|
+
with Computer().create(type="macos", reservation_id="...") as mac:
|
|
60
|
+
print(mac.exec_ssh("uname -a").stdout)
|
|
54
61
|
```
|
|
55
62
|
|
|
56
63
|
### Windows (Beta)
|
|
@@ -114,12 +121,16 @@ that snapshot version.
|
|
|
114
121
|
```python
|
|
115
122
|
client = Computer()
|
|
116
123
|
|
|
124
|
+
# Configure the desktop by hand over VNC, then snapshot it.
|
|
117
125
|
with client.create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
|
|
118
|
-
|
|
126
|
+
print("Set up the desktop here:", ubuntu.vnc_url)
|
|
127
|
+
# install apps, sign into accounts, pre-open the windows your agent starts from
|
|
128
|
+
input("Press Enter once the desktop is ready to snapshot...")
|
|
119
129
|
snapshot = ubuntu.snapshot("chrome-seeded-ubuntu")
|
|
120
130
|
|
|
131
|
+
# New sandboxes boot from that exact state, no setup needed.
|
|
121
132
|
with client.create(type="ubuntu", snapshot=snapshot.version) as seeded:
|
|
122
|
-
print(seeded.
|
|
133
|
+
print(seeded.vnc_url) # apps, logins, and open windows restored
|
|
123
134
|
```
|
|
124
135
|
|
|
125
136
|
Use `client.snapshots("ubuntu")` or `client.snapshots("windows")` to list saved
|
|
@@ -164,7 +175,7 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
164
175
|
| [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
|
|
165
176
|
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows desktop state |
|
|
166
177
|
|
|
167
|
-
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook).
|
|
178
|
+
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Harbor job YAMLs should use Harbor's `type: use-computer` environment plus `use_computer.harbor.agents:*` agent wrappers.
|
|
168
179
|
|
|
169
180
|
## Skill for AI coding assistants
|
|
170
181
|
|
|
@@ -172,4 +183,4 @@ Point your assistant at [`use-computer-cookbook/skills/SKILL.md`](https://github
|
|
|
172
183
|
|
|
173
184
|
## HTTP API
|
|
174
185
|
|
|
175
|
-
Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer
|
|
186
|
+
Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "use-computer"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.43"
|
|
8
8
|
description = "Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -45,7 +45,7 @@ from use_computer.agents import (
|
|
|
45
45
|
OpenAIComputerAgent,
|
|
46
46
|
)
|
|
47
47
|
from use_computer.agents.debug import DebugComputerAgent
|
|
48
|
-
from use_computer.models import ExecResult
|
|
48
|
+
from use_computer.core.models import ExecResult
|
|
49
49
|
|
|
50
50
|
PNG_BYTES = (
|
|
51
51
|
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
|
|
@@ -116,14 +116,14 @@ import importlib.util
|
|
|
116
116
|
|
|
117
117
|
for name in ["harbor", "yaml", "tomli"]:
|
|
118
118
|
assert importlib.util.find_spec(name) is not None, name
|
|
119
|
-
for path in ["use_computer.harbor"
|
|
119
|
+
for path in ["use_computer.harbor"]:
|
|
120
120
|
importlib.import_module(path)
|
|
121
121
|
"""
|
|
122
122
|
|
|
123
123
|
HARBOR_AGENTS_SMOKE = r"""
|
|
124
124
|
import importlib
|
|
125
125
|
|
|
126
|
-
for path in ["use_computer.harbor
|
|
126
|
+
for path in ["use_computer.harbor", "use_computer.harbor.agents"]:
|
|
127
127
|
importlib.import_module(path)
|
|
128
128
|
"""
|
|
129
129
|
|
|
@@ -156,7 +156,6 @@ def _check_wheel_contents(wheel: Path) -> None:
|
|
|
156
156
|
"use_computer/agents/prompts/ios.txt",
|
|
157
157
|
"use_computer/agents/prompts/openai.txt",
|
|
158
158
|
"use_computer/agents/prompts/pyautogui.txt",
|
|
159
|
-
"use_computer/harbor/environment/use_computer.py",
|
|
160
159
|
"use_computer/harbor/agents.py",
|
|
161
160
|
}
|
|
162
161
|
missing = sorted(required - names)
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
from use_computer.
|
|
2
|
-
from use_computer.
|
|
3
|
-
from use_computer.
|
|
4
|
-
from use_computer.
|
|
1
|
+
from use_computer.automation.osworld import AsyncOSWorldClient, OSWorldClient
|
|
2
|
+
from use_computer.automation.parsers import Action, parse_pyautogui, parse_xdotool
|
|
3
|
+
from use_computer.core.client import AsyncComputer, Computer, Reservation, RunStatus
|
|
4
|
+
from use_computer.core.errors import (
|
|
5
|
+
BillingRequiredError,
|
|
6
|
+
PlatformNotSupportedError,
|
|
7
|
+
ReservationRequiredError,
|
|
8
|
+
UseComputerError,
|
|
9
|
+
)
|
|
10
|
+
from use_computer.core.models import (
|
|
5
11
|
AccessibilityTree,
|
|
6
12
|
ActionResult,
|
|
7
13
|
ActResult,
|
|
@@ -10,8 +16,7 @@ from use_computer.models import (
|
|
|
10
16
|
ExecResult,
|
|
11
17
|
RecordingInfo,
|
|
12
18
|
)
|
|
13
|
-
from use_computer.
|
|
14
|
-
from use_computer.sandbox import (
|
|
19
|
+
from use_computer.core.sandbox import (
|
|
15
20
|
AsyncIOSSandbox,
|
|
16
21
|
AsyncMacOSSandbox,
|
|
17
22
|
AsyncSandbox,
|
|
@@ -26,7 +31,8 @@ from use_computer.sandbox import (
|
|
|
26
31
|
UbuntuSandbox,
|
|
27
32
|
WindowsSandbox,
|
|
28
33
|
)
|
|
29
|
-
from use_computer.
|
|
34
|
+
from use_computer.ios.input import Button, Key, RemoteButton
|
|
35
|
+
from use_computer.ios.simulators import (
|
|
30
36
|
SimulatorChoice,
|
|
31
37
|
SimulatorFamily,
|
|
32
38
|
family_for_device,
|
|
@@ -45,9 +51,11 @@ __all__ = [
|
|
|
45
51
|
"AsyncIOSSandbox",
|
|
46
52
|
"AsyncMacOSSandbox",
|
|
47
53
|
"AsyncComputer",
|
|
54
|
+
"AsyncOSWorldClient",
|
|
48
55
|
"AsyncSandbox",
|
|
49
56
|
"AsyncUbuntuSandbox",
|
|
50
57
|
"AsyncWindowsSandbox",
|
|
58
|
+
"BillingRequiredError",
|
|
51
59
|
"Button",
|
|
52
60
|
"Computer",
|
|
53
61
|
"CursorPosition",
|
|
@@ -57,8 +65,11 @@ __all__ = [
|
|
|
57
65
|
"IOSSandbox",
|
|
58
66
|
"Key",
|
|
59
67
|
"MacOSSandbox",
|
|
68
|
+
"OSWorldClient",
|
|
60
69
|
"PlatformNotSupportedError",
|
|
61
70
|
"RecordingInfo",
|
|
71
|
+
"Reservation",
|
|
72
|
+
"ReservationRequiredError",
|
|
62
73
|
"RemoteButton",
|
|
63
74
|
"RunStatus",
|
|
64
75
|
"Sandbox",
|
|
@@ -26,6 +26,8 @@ _LAZY_EXPORTS = {
|
|
|
26
26
|
"IOSComputerAgent": ("use_computer.agents.ios.agent", "IOSComputerAgent"),
|
|
27
27
|
"OpenAIComputerAgent": ("use_computer.agents.providers.openai", "OpenAIComputerAgent"),
|
|
28
28
|
"OpenAICUAAgent": ("use_computer.agents.providers.openai", "OpenAICUAAgent"),
|
|
29
|
+
"OSWorldAgent": ("use_computer.agents.providers.osworld", "OSWorldAgent"),
|
|
30
|
+
"OSWorldComputerAgent": ("use_computer.agents.providers.osworld", "OSWorldComputerAgent"),
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
__all__ = [
|
|
@@ -16,6 +16,7 @@ from use_computer.agents.base.desktop import (
|
|
|
16
16
|
from use_computer.agents.base.final_state import FinalStateMixin
|
|
17
17
|
from use_computer.agents.base.recordings import RecordingMixin
|
|
18
18
|
from use_computer.agents.base.sandbox import ComputerSandbox
|
|
19
|
+
from use_computer.agents.base.task_setup import run_task_setup
|
|
19
20
|
from use_computer.agents.base.trajectory import write_trajectory
|
|
20
21
|
from use_computer.agents.core import (
|
|
21
22
|
AgentRunOptions,
|
|
@@ -25,7 +26,7 @@ from use_computer.agents.core import (
|
|
|
25
26
|
DirectSandboxEnvironment,
|
|
26
27
|
)
|
|
27
28
|
from use_computer.agents.types import Platform
|
|
28
|
-
from use_computer.sandbox import AsyncSandbox
|
|
29
|
+
from use_computer.core.sandbox import AsyncSandbox
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def _task_dir_from_env(environment: Any) -> Path:
|
|
@@ -59,6 +60,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
|
59
60
|
platform_notes: str | None = None,
|
|
60
61
|
openai_environment: OpenAIComputerEnvironment | str | None = None,
|
|
61
62
|
save_on_finish: bool | str = False,
|
|
63
|
+
recording_enabled: bool | str = True,
|
|
64
|
+
final_state_enabled: bool | str = True,
|
|
62
65
|
**kwargs: Any,
|
|
63
66
|
):
|
|
64
67
|
self.logs_dir = Path(logs_dir or "runs/agent").resolve()
|
|
@@ -78,6 +81,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
|
78
81
|
self.desktop = self._desktop_profile_for(platform or Platform.MACOS)
|
|
79
82
|
self.task_dir: Path | None = Path(task_dir) if task_dir else None
|
|
80
83
|
self._recording_id: str | None = None
|
|
84
|
+
self.recording_enabled = _as_bool(recording_enabled)
|
|
85
|
+
self.final_state_enabled = _as_bool(final_state_enabled)
|
|
81
86
|
self.sandbox: AsyncSandbox | None = None
|
|
82
87
|
self.images_dir: Path = (self.logs_dir / "images").resolve()
|
|
83
88
|
self.steps: list[dict[str, Any]] = []
|
|
@@ -120,7 +125,7 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
|
120
125
|
return "1.0.0"
|
|
121
126
|
|
|
122
127
|
async def setup(self, environment: Any) -> None:
|
|
123
|
-
|
|
128
|
+
await run_task_setup(environment, self.logger)
|
|
124
129
|
|
|
125
130
|
async def run(
|
|
126
131
|
self,
|
|
@@ -164,7 +169,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
|
164
169
|
if self.task_dir is None:
|
|
165
170
|
self.task_dir = _task_dir_from_env(environment)
|
|
166
171
|
self.logger.info(f"task_dir={self.task_dir}")
|
|
167
|
-
|
|
172
|
+
if self.recording_enabled:
|
|
173
|
+
await self.start_recording(sandbox)
|
|
168
174
|
self.images_dir.mkdir(parents=True, exist_ok=True)
|
|
169
175
|
self.steps = [{"step_id": 1, "source": "user", "message": ""}]
|
|
170
176
|
self.total_in = 0
|
|
@@ -193,10 +199,12 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
|
193
199
|
assert self.sandbox is not None
|
|
194
200
|
if self.save_on_finish:
|
|
195
201
|
await self._save_focused_app(self.sandbox)
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
if self.final_state_enabled:
|
|
203
|
+
await self._save_final_ax_tree(self.sandbox)
|
|
204
|
+
await self._log_final_state_metrics(self.sandbox)
|
|
198
205
|
await self._capture_artifacts(context)
|
|
199
|
-
|
|
206
|
+
if self.recording_enabled:
|
|
207
|
+
await self.stop_recording(self.sandbox)
|
|
200
208
|
self.checkpoint(context, model, agent_name)
|
|
201
209
|
|
|
202
210
|
async def run_direct(
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Recording helpers for CUA agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from use_computer.core.sandbox import AsyncSandbox
|
|
15
|
+
|
|
16
|
+
_OSWORLD_FFMPEG_RECORDING_ID = "__osworld_ffmpeg__"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RecordingMixin:
|
|
20
|
+
async def start_recording(self: Any, sandbox: AsyncSandbox) -> None:
|
|
21
|
+
if getattr(getattr(sandbox, "type", None), "value", "") == "ios":
|
|
22
|
+
try:
|
|
23
|
+
await sandbox.screenshot.take_full_screen()
|
|
24
|
+
except Exception as e:
|
|
25
|
+
self.logger.warning(f"iOS recording warm-up screenshot failed: {e}")
|
|
26
|
+
try:
|
|
27
|
+
result = await sandbox.recording.start(name="trial")
|
|
28
|
+
self._recording_id = result.id
|
|
29
|
+
self.logger.info(f"Recording started: {self._recording_id}")
|
|
30
|
+
except Exception as e:
|
|
31
|
+
self.logger.warning(f"Failed to start recording: {e}")
|
|
32
|
+
if await self._start_osworld_ffmpeg_recording(sandbox):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
async def stop_recording(self: Any, sandbox: AsyncSandbox) -> None:
|
|
36
|
+
if not self._recording_id:
|
|
37
|
+
return
|
|
38
|
+
await asyncio.sleep(3.0)
|
|
39
|
+
rec_path = self.logs_dir / "recording.mp4"
|
|
40
|
+
if self._recording_id == _OSWORLD_FFMPEG_RECORDING_ID:
|
|
41
|
+
await self._stop_osworld_ffmpeg_recording(sandbox, rec_path)
|
|
42
|
+
self._recording_id = None
|
|
43
|
+
return
|
|
44
|
+
try:
|
|
45
|
+
info = await sandbox.recording.stop(self._recording_id)
|
|
46
|
+
self.logger.info(
|
|
47
|
+
f"Recording stopped: id={self._recording_id} "
|
|
48
|
+
f"name={info.name!r} filename={info.filename!r} "
|
|
49
|
+
f"server_size={info.file_size}B "
|
|
50
|
+
f"({info.file_size / (1024 * 1024):.2f} MB)"
|
|
51
|
+
)
|
|
52
|
+
if info.file_size <= 0:
|
|
53
|
+
raise RuntimeError("server recording is empty")
|
|
54
|
+
|
|
55
|
+
await asyncio.sleep(2)
|
|
56
|
+
|
|
57
|
+
await self._download_recording(sandbox, rec_path)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self.logger.warning(f"Recording save failed: {type(e).__name__}: {e}")
|
|
60
|
+
try:
|
|
61
|
+
await self._download_recording(sandbox, rec_path)
|
|
62
|
+
except Exception as fallback:
|
|
63
|
+
self.logger.warning(
|
|
64
|
+
f"Recording fallback download failed: {type(fallback).__name__}: {fallback}"
|
|
65
|
+
)
|
|
66
|
+
self._write_screenshot_recording(
|
|
67
|
+
rec_path,
|
|
68
|
+
reason=(
|
|
69
|
+
f"server recording failed: {type(e).__name__}: {e}; "
|
|
70
|
+
f"download fallback failed: {type(fallback).__name__}: {fallback}"
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def _download_recording(self: Any, sandbox: AsyncSandbox, rec_path) -> None:
|
|
75
|
+
self.logger.info(f"Downloading recording → {rec_path}")
|
|
76
|
+
t_dl = time.monotonic()
|
|
77
|
+
await sandbox.recording.download(self._recording_id, str(rec_path), timeout=180.0)
|
|
78
|
+
dl_elapsed = time.monotonic() - t_dl
|
|
79
|
+
local_size = rec_path.stat().st_size
|
|
80
|
+
if local_size <= 0:
|
|
81
|
+
raise RuntimeError("downloaded recording is empty")
|
|
82
|
+
self.logger.info(
|
|
83
|
+
f"Recording saved: {rec_path} "
|
|
84
|
+
f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
|
|
85
|
+
f"download_elapsed={dl_elapsed:.1f}s"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _write_screenshot_recording(self: Any, rec_path: Path, reason: str) -> bool:
|
|
89
|
+
image_dir = self.logs_dir / "images"
|
|
90
|
+
images = sorted(image_dir.glob("*.png")) if image_dir.exists() else []
|
|
91
|
+
if not images:
|
|
92
|
+
self.logger.warning("Screenshot recording fallback skipped: no saved screenshots")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
ffmpeg = shutil.which("ffmpeg")
|
|
96
|
+
if not ffmpeg:
|
|
97
|
+
self.logger.warning("Screenshot recording fallback skipped: ffmpeg not found")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
from PIL import Image
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self.logger.warning(f"Screenshot recording fallback skipped: Pillow unavailable: {e}")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
rec_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
t0 = time.monotonic()
|
|
108
|
+
try:
|
|
109
|
+
with Image.open(images[0]) as first:
|
|
110
|
+
width, height = first.size
|
|
111
|
+
width -= width % 2
|
|
112
|
+
height -= height % 2
|
|
113
|
+
if width <= 0 or height <= 0:
|
|
114
|
+
raise RuntimeError(f"invalid screenshot size {width}x{height}")
|
|
115
|
+
|
|
116
|
+
with tempfile.TemporaryDirectory(prefix="use-computer-recording-") as tmp:
|
|
117
|
+
tmp_dir = Path(tmp)
|
|
118
|
+
frame_count = 0
|
|
119
|
+
for idx, src in enumerate(images, start=1):
|
|
120
|
+
with Image.open(src) as img:
|
|
121
|
+
frame = img.convert("RGB")
|
|
122
|
+
if frame.size != (width, height):
|
|
123
|
+
frame = frame.resize((width, height))
|
|
124
|
+
frame.save(tmp_dir / f"frame{idx:06d}.png")
|
|
125
|
+
frame_count += 1
|
|
126
|
+
|
|
127
|
+
if frame_count == 1:
|
|
128
|
+
shutil.copyfile(tmp_dir / "frame000001.png", tmp_dir / "frame000002.png")
|
|
129
|
+
frame_count = 2
|
|
130
|
+
|
|
131
|
+
cmd = [
|
|
132
|
+
ffmpeg,
|
|
133
|
+
"-y",
|
|
134
|
+
"-loglevel",
|
|
135
|
+
"error",
|
|
136
|
+
"-framerate",
|
|
137
|
+
"1",
|
|
138
|
+
"-i",
|
|
139
|
+
str(tmp_dir / "frame%06d.png"),
|
|
140
|
+
"-vf",
|
|
141
|
+
"format=yuv420p",
|
|
142
|
+
"-movflags",
|
|
143
|
+
"+faststart",
|
|
144
|
+
str(rec_path),
|
|
145
|
+
]
|
|
146
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
147
|
+
if result.returncode != 0:
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
result.stderr.strip() or f"ffmpeg exited {result.returncode}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
local_size = rec_path.stat().st_size
|
|
153
|
+
if local_size <= 0:
|
|
154
|
+
raise RuntimeError("ffmpeg wrote an empty recording")
|
|
155
|
+
|
|
156
|
+
meta = {
|
|
157
|
+
"source": "screenshots",
|
|
158
|
+
"reason": reason,
|
|
159
|
+
"frames": len(images),
|
|
160
|
+
"width": width,
|
|
161
|
+
"height": height,
|
|
162
|
+
"file_size": local_size,
|
|
163
|
+
"elapsed_sec": round(time.monotonic() - t0, 3),
|
|
164
|
+
}
|
|
165
|
+
(self.logs_dir / "recording.json").write_text(json.dumps(meta, indent=2))
|
|
166
|
+
self.logger.info(
|
|
167
|
+
f"Screenshot recording fallback saved: {rec_path} "
|
|
168
|
+
f"frames={len(images)} local_size={local_size}B "
|
|
169
|
+
f"({local_size / (1024 * 1024):.2f} MB)"
|
|
170
|
+
)
|
|
171
|
+
return True
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.logger.warning(f"Screenshot recording fallback failed: {type(e).__name__}: {e}")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
async def _start_osworld_ffmpeg_recording(self: Any, sandbox: AsyncSandbox) -> bool:
|
|
177
|
+
osworld: Any = getattr(sandbox, "osworld", None)
|
|
178
|
+
if osworld is None:
|
|
179
|
+
return False
|
|
180
|
+
if getattr(getattr(sandbox, "type", None), "value", "") != "ubuntu":
|
|
181
|
+
self.logger.warning("OSWorld ffmpeg recording fallback is only implemented for Ubuntu")
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
script = """
|
|
186
|
+
set -eux
|
|
187
|
+
rm -f /tmp/osworld-recording.mp4 /tmp/osworld-recording.log /tmp/osworld-recording.pid
|
|
188
|
+
export DISPLAY=:0
|
|
189
|
+
nohup ffmpeg -y \
|
|
190
|
+
-video_size 1920x1080 \
|
|
191
|
+
-framerate 2 \
|
|
192
|
+
-f x11grab \
|
|
193
|
+
-i :0 \
|
|
194
|
+
-t 120 \
|
|
195
|
+
-pix_fmt yuv420p \
|
|
196
|
+
/tmp/osworld-recording.mp4 > /tmp/osworld-recording.log 2>&1 &
|
|
197
|
+
echo $! > /tmp/osworld-recording.pid
|
|
198
|
+
cat /tmp/osworld-recording.pid
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
result = await osworld.execute(["bash", "-lc", script], timeout=10)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self.logger.warning(f"OSWorld ffmpeg recording start failed: {type(e).__name__}: {e}")
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
(self.logs_dir / "recording-start.json").write_text(json.dumps(result, indent=2))
|
|
207
|
+
if result.get("returncode") != 0:
|
|
208
|
+
self.logger.warning(f"OSWorld ffmpeg recording start failed: {result}")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
self._recording_id = _OSWORLD_FFMPEG_RECORDING_ID
|
|
212
|
+
self.logger.info("OSWorld ffmpeg recording started")
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
async def _stop_osworld_ffmpeg_recording(self: Any, sandbox: AsyncSandbox, rec_path) -> None:
|
|
216
|
+
osworld: Any = getattr(sandbox, "osworld", None)
|
|
217
|
+
if osworld is None:
|
|
218
|
+
return
|
|
219
|
+
stop_script = """
|
|
220
|
+
set -eux
|
|
221
|
+
if [ -f /tmp/osworld-recording.pid ]; then
|
|
222
|
+
pid="$(cat /tmp/osworld-recording.pid)"
|
|
223
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
224
|
+
kill -INT "$pid" || true
|
|
225
|
+
fi
|
|
226
|
+
for _ in $(seq 1 40); do
|
|
227
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
228
|
+
sleep 0.25
|
|
229
|
+
else
|
|
230
|
+
break
|
|
231
|
+
fi
|
|
232
|
+
done
|
|
233
|
+
fi
|
|
234
|
+
ls -lh /tmp/osworld-recording.mp4 /tmp/osworld-recording.log
|
|
235
|
+
tail -80 /tmp/osworld-recording.log
|
|
236
|
+
"""
|
|
237
|
+
t_dl = time.monotonic()
|
|
238
|
+
try:
|
|
239
|
+
meta: dict[str, Any] = {
|
|
240
|
+
"stop": await osworld.execute(["bash", "-lc", stop_script], timeout=30)
|
|
241
|
+
}
|
|
242
|
+
data = await osworld.file("/tmp/osworld-recording.mp4")
|
|
243
|
+
rec_path.write_bytes(data)
|
|
244
|
+
local_size = rec_path.stat().st_size
|
|
245
|
+
meta["downloaded_size"] = local_size
|
|
246
|
+
(self.logs_dir / "recording.json").write_text(json.dumps(meta, indent=2))
|
|
247
|
+
self.logger.info(
|
|
248
|
+
f"OSWorld ffmpeg recording saved: {rec_path} "
|
|
249
|
+
f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
|
|
250
|
+
f"download_elapsed={time.monotonic() - t_dl:.1f}s"
|
|
251
|
+
)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
self.logger.warning(f"OSWorld ffmpeg recording save failed: {type(e).__name__}: {e}")
|