gemini-computer-use 0.2.3 → 0.4.1
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.
- package/README.md +935 -148
- package/bin/cli.js +112 -17
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/agent.cpython-313.pyc +0 -0
- package/core/__pycache__/code_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/mcp_tools.cpython-313.pyc +0 -0
- package/core/__pycache__/schemas.cpython-313.pyc +0 -0
- package/core/__pycache__/server.cpython-313.pyc +0 -0
- package/core/__pycache__/vitals.cpython-313.pyc +0 -0
- package/core/__pycache__/web_share.cpython-313.pyc +0 -0
- package/core/agent.py +688 -27
- package/core/agent_harness.py +9 -2
- package/core/code_cache.py +15 -3
- package/core/device.py +2400 -0
- package/core/domain.py +303 -0
- package/core/mcp_tools.py +1949 -110
- package/core/policies.py +35 -5
- package/core/server.py +78 -32
- package/core/termux.py +916 -0
- package/core/updater.py +1713 -0
- package/core/version.py +31 -0
- package/core/vitals.py +66 -2
- package/core/web_share.py +814 -0
- package/gateway.py +1469 -98
- package/install-gui.cmd +40 -0
- package/install-gui.ps1 +1882 -0
- package/install-gui.strings.json +211 -0
- package/install.ps1 +960 -92
- package/install.sh +1214 -81
- package/ops/doctor.ps1 +735 -0
- package/ops/mesh-domain.sh +86 -0
- package/ops/nginx/README.md +77 -0
- package/ops/nginx/antigravity-mesh-mcp.conf +172 -0
- package/ops/nginx/mesh-domain.vhost.template +58 -0
- package/ops/nginx/render-domain.sh +123 -0
- package/ops/update.ps1 +373 -0
- package/ops/windows/README.md +298 -0
- package/ops/windows/agent-watchdog.ps1 +575 -0
- package/ops/windows/run-hidden.vbs +64 -0
- package/package.json +6 -2
- package/skills/orchestrator.md +12 -0
package/core/policies.py
CHANGED
|
@@ -5,12 +5,27 @@ from google.antigravity import types
|
|
|
5
5
|
from google.antigravity.hooks import policy
|
|
6
6
|
from google.antigravity.hooks.policy import Policy, Decision
|
|
7
7
|
|
|
8
|
+
def _platform_root(posix_root: str) -> str:
|
|
9
|
+
"""Express a documented deployment root for the host running this node.
|
|
10
|
+
|
|
11
|
+
``/root/...`` is an absolute path on Linux but only drive-relative on Windows,
|
|
12
|
+
where the SDK refuses it outright ("app_data_dir must be an absolute path") and
|
|
13
|
+
``os.makedirs`` would scatter a ``\\root\\...`` tree beside the drive root. The
|
|
14
|
+
POSIX path is kept unchanged on POSIX hosts, so deployed services do not move.
|
|
15
|
+
"""
|
|
16
|
+
if os.name != "nt":
|
|
17
|
+
return posix_root
|
|
18
|
+
leaf = posix_root.rstrip("/").rsplit("/", 1)[-1]
|
|
19
|
+
return os.path.join(os.path.expanduser("~"), leaf)
|
|
20
|
+
|
|
21
|
+
|
|
8
22
|
# Workspace roots are read from the environment ONLY -- never hardcode a personal
|
|
9
23
|
# home directory. Point MESH_COORDINATOR_WORKSPACE / MESH_WORKSPACE at the real
|
|
10
|
-
# deployment paths in the service environment.
|
|
24
|
+
# deployment paths in the service environment. The documented defaults are the
|
|
25
|
+
# Linux deployment paths; on Windows the same roots live under the user profile.
|
|
11
26
|
ALLOWED_WORKSPACES = [
|
|
12
|
-
os.environ.get("MESH_COORDINATOR_WORKSPACE"
|
|
13
|
-
os.environ.get("MESH_WORKSPACE"
|
|
27
|
+
os.environ.get("MESH_COORDINATOR_WORKSPACE") or _platform_root("/root/agy-gdrive-runner"),
|
|
28
|
+
os.environ.get("MESH_WORKSPACE") or _platform_root("/root/antigravity-mesh"),
|
|
14
29
|
]
|
|
15
30
|
|
|
16
31
|
SAFE_COMMAND_PREFIXES = [
|
|
@@ -40,12 +55,27 @@ DESTRUCTIVE_COMMAND_PATTERNS = [
|
|
|
40
55
|
re.compile(r"\bdd\s+"),
|
|
41
56
|
]
|
|
42
57
|
|
|
58
|
+
def _is_within(path: str, root: str) -> bool:
|
|
59
|
+
"""True when *path* is *root* itself or lives inside it.
|
|
60
|
+
|
|
61
|
+
The comparison has to use the platform separator: ``root + "/"`` never
|
|
62
|
+
matches a normpath'ed Windows path, so every file inside an allowed workspace
|
|
63
|
+
was rejected there and the policy could not be satisfied at all.
|
|
64
|
+
"""
|
|
65
|
+
if path == root:
|
|
66
|
+
return True
|
|
67
|
+
root = root.rstrip("/\\")
|
|
68
|
+
for sep in {os.sep, "/", "\\"}:
|
|
69
|
+
if path.startswith(root + sep):
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
43
74
|
def validate_workspace_path(path: str, allowed_workspaces: Optional[List[str]] = None) -> bool:
|
|
44
75
|
workspaces = allowed_workspaces or ALLOWED_WORKSPACES
|
|
45
76
|
abs_path = os.path.abspath(path)
|
|
46
77
|
for ws in workspaces:
|
|
47
|
-
|
|
48
|
-
if abs_path == ws_abs or abs_path.startswith(ws_abs.rstrip("/") + "/"):
|
|
78
|
+
if _is_within(abs_path, os.path.abspath(ws)):
|
|
49
79
|
return True
|
|
50
80
|
return False
|
|
51
81
|
|
package/core/server.py
CHANGED
|
@@ -169,47 +169,72 @@ def get_coordinator_vitals() -> "NodeVitals":
|
|
|
169
169
|
except Exception:
|
|
170
170
|
load = 0.0
|
|
171
171
|
|
|
172
|
+
# ``/proc/meminfo`` and ``/proc/uptime`` are Linux-only. Ask the shared
|
|
173
|
+
# cross-platform collector first (core.vitals knows the Windows and macOS
|
|
174
|
+
# paths), and keep the inline /proc parsing as the last resort.
|
|
175
|
+
native: Optional[Dict[str, Any]] = None
|
|
176
|
+
if _core_get_host_vitals is not None:
|
|
177
|
+
try:
|
|
178
|
+
candidate = _core_get_host_vitals()
|
|
179
|
+
if isinstance(candidate, dict):
|
|
180
|
+
native = candidate
|
|
181
|
+
except Exception:
|
|
182
|
+
native = None
|
|
183
|
+
|
|
172
184
|
total_mb = 1
|
|
173
185
|
used_mb = 0
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
if native:
|
|
187
|
+
ram = native.get("ram") or {}
|
|
188
|
+
try:
|
|
189
|
+
total_mb = int(ram.get("total_mb") or 0) or 1
|
|
190
|
+
used_mb = max(0, int(ram.get("used_mb") or 0))
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
total_mb, used_mb = 1, 0
|
|
193
|
+
else:
|
|
194
|
+
try:
|
|
195
|
+
with open("/proc/meminfo", "r") as f:
|
|
196
|
+
lines = f.readlines()
|
|
197
|
+
mem = {}
|
|
198
|
+
for line in lines:
|
|
199
|
+
parts = line.split(":")
|
|
200
|
+
if len(parts) == 2:
|
|
201
|
+
k = parts[0].strip()
|
|
202
|
+
v = parts[1].strip().split()[0]
|
|
203
|
+
if k in ["MemTotal", "MemAvailable"]:
|
|
204
|
+
mem[k] = int(v) // 1024
|
|
205
|
+
if "MemTotal" in mem and "MemAvailable" in mem:
|
|
206
|
+
total_mb = mem["MemTotal"]
|
|
207
|
+
avail_mb = mem["MemAvailable"]
|
|
208
|
+
used_mb = max(0, total_mb - avail_mb)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
191
211
|
|
|
192
212
|
usage_pct = round((used_mb / total_mb) * 100.0, 2) if total_mb > 0 else 0.0
|
|
193
213
|
usage_pct = min(100.0, max(0.0, usage_pct))
|
|
194
214
|
|
|
195
215
|
total_disk_gb = 1.0
|
|
196
216
|
free_disk_gb = 0.0
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
if native:
|
|
218
|
+
disk = native.get("disk") or {}
|
|
219
|
+
try:
|
|
220
|
+
total_disk_gb = float(disk.get("total_gb") or 0.0) or 1.0
|
|
221
|
+
free_disk_gb = max(0.0, float(disk.get("free_gb") or 0.0))
|
|
222
|
+
except (TypeError, ValueError):
|
|
223
|
+
total_disk_gb, free_disk_gb = 1.0, 0.0
|
|
224
|
+
else:
|
|
225
|
+
try:
|
|
226
|
+
d = shutil.disk_usage("C:\\" if platform.system() == "Windows" else "/")
|
|
227
|
+
total_disk_gb = round(d.total / (1024 ** 3), 2)
|
|
228
|
+
free_disk_gb = round(d.free / (1024 ** 3), 2)
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
203
231
|
|
|
204
232
|
uptime_str = "unknown"
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
uptime_str = f"{hours}h {mins}m"
|
|
211
|
-
except Exception:
|
|
212
|
-
pass
|
|
233
|
+
uptime_seconds = mcp_tools.host_uptime_seconds()
|
|
234
|
+
if uptime_seconds is not None:
|
|
235
|
+
hours = int(uptime_seconds // 3600)
|
|
236
|
+
mins = int((uptime_seconds % 3600) // 60)
|
|
237
|
+
uptime_str = f"{hours}h {mins}m"
|
|
213
238
|
|
|
214
239
|
fields = dict(
|
|
215
240
|
hostname=hostname,
|
|
@@ -299,6 +324,9 @@ _REQUIRED_ARGS: Dict[str, Tuple[str, ...]] = {
|
|
|
299
324
|
"run_job": ("command",),
|
|
300
325
|
"job_output": ("job_id",),
|
|
301
326
|
"job_kill": ("job_id",),
|
|
327
|
+
"share_file": ("path",),
|
|
328
|
+
"serve_dir": ("path",),
|
|
329
|
+
"unshare": ("name",),
|
|
302
330
|
}
|
|
303
331
|
|
|
304
332
|
|
|
@@ -440,7 +468,16 @@ class AntigravityMeshServer(object):
|
|
|
440
468
|
"resources": {"subscribe": False, "listChanged": False},
|
|
441
469
|
},
|
|
442
470
|
"serverInfo": {"name": self.name, "version": self.version},
|
|
443
|
-
|
|
471
|
+
# Short on purpose: the tool descriptions carry the detail. The
|
|
472
|
+
# one thing worth saying up front is where the device branch
|
|
473
|
+
# lives, because a phone node (Termux) is exactly the case where
|
|
474
|
+
# a model otherwise hand-writes termux-* commands through
|
|
475
|
+
# bash_exec instead of using the typed tools.
|
|
476
|
+
"instructions": ("Standalone Local Antigravity Mesh Server. "
|
|
477
|
+
"For the device this node runs on - battery, signal, "
|
|
478
|
+
"time, sensors, cameras, microphones, notifications, "
|
|
479
|
+
"torch, volume - use device_info / device_control; "
|
|
480
|
+
"device_capture and device_messages ask the user first."),
|
|
444
481
|
},
|
|
445
482
|
}
|
|
446
483
|
|
|
@@ -613,6 +650,15 @@ def configure_tools_from_env() -> None:
|
|
|
613
650
|
write_roots=os.environ.get("MESH_WRITE_ROOTS") or None,
|
|
614
651
|
jobs_dir=os.environ.get("MESH_JOBS_DIR") or None,
|
|
615
652
|
max_output_chars=os.environ.get("MESH_MAX_OUTPUT_CHARS"),
|
|
653
|
+
# Public shares. ``public_url`` is deliberately NOT passed: the public
|
|
654
|
+
# domain is resolved by core/domain.py (MESH_PUBLIC_URL, domain.env, then
|
|
655
|
+
# the one default), and pinning it here to the environment variable alone
|
|
656
|
+
# would bypass the domain file.
|
|
657
|
+
web_dir=os.environ.get("MESH_WEB_DIR") or None,
|
|
658
|
+
mesh_user=os.environ.get("MESH_USER") or None,
|
|
659
|
+
max_share_bytes=os.environ.get("MESH_WEB_MAX_BYTES"),
|
|
660
|
+
max_shares=os.environ.get("MESH_WEB_MAX_SHARES"),
|
|
661
|
+
web_listing=os.environ.get("MESH_WEB_LISTING"),
|
|
616
662
|
)
|
|
617
663
|
|
|
618
664
|
|