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/domain.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""core/domain.py - the ONE place that knows the project's public domain.
|
|
4
|
+
|
|
5
|
+
Every component that has to name the public host asks this module instead of
|
|
6
|
+
building its own chain of fallbacks:
|
|
7
|
+
|
|
8
|
+
* :mod:`gateway` (URLs and texts handed to clients),
|
|
9
|
+
* :mod:`core.agent` (the host a node dials for its tunnel),
|
|
10
|
+
* :mod:`core.web_share` (links published for shared files),
|
|
11
|
+
* :mod:`core.mcp_tools` (the ``public_url`` it passes on),
|
|
12
|
+
* :mod:`agy_sync`, :mod:`agy_watcher` (links printed into generated facts/logs).
|
|
13
|
+
|
|
14
|
+
Resolution order, highest priority first:
|
|
15
|
+
|
|
16
|
+
1. ``configure(public_base_url=...)`` - explicit injection, for tests;
|
|
17
|
+
2. ``MESH_PUBLIC_URL`` in the environment;
|
|
18
|
+
3. ``AGY_PUBLIC_BASE_URL`` in the environment (legacy alias, same meaning);
|
|
19
|
+
4. ``MESH_PUBLIC_URL`` (or ``AGY_PUBLIC_BASE_URL``) in the *domain file* - the one
|
|
20
|
+
host-level place to configure this:
|
|
21
|
+
|
|
22
|
+
========== ==========================================================
|
|
23
|
+
Linux ``/etc/antigravity-mesh/domain.env``
|
|
24
|
+
Windows ``%USERPROFILE%\\.config\\antigravity-mesh\\domain.env``
|
|
25
|
+
Termux ``~/.config/antigravity-mesh/domain.env``
|
|
26
|
+
========== ==========================================================
|
|
27
|
+
|
|
28
|
+
``domain.env`` is plain ``KEY=VALUE`` and may carry a UTF-8 BOM, because
|
|
29
|
+
Windows PowerShell writes one. Point ``MESH_DOMAIN_FILE`` at another path to
|
|
30
|
+
override the location (used by the tests);
|
|
31
|
+
5. ``MESH_GATEWAY`` in the environment - the legacy node-side setting. It is
|
|
32
|
+
honoured for the *tunnel host* only, so a node installed before ``domain.env``
|
|
33
|
+
existed keeps working until its operator moves the value into the domain file;
|
|
34
|
+
6. :data:`DEFAULT_PUBLIC_BASE_URL` - the single default, declared once, here.
|
|
35
|
+
|
|
36
|
+
Nothing in this module imports another project module, and only the standard
|
|
37
|
+
library is used, so any component may import it - including ``gateway.py`` when it
|
|
38
|
+
is deployed on its own.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import os
|
|
42
|
+
import threading
|
|
43
|
+
from typing import Any, Dict, Optional
|
|
44
|
+
from urllib.parse import urlparse
|
|
45
|
+
|
|
46
|
+
#: The project default. Declared exactly once; every other module asks for it.
|
|
47
|
+
DEFAULT_PUBLIC_BASE_URL = "https://smart-server.online"
|
|
48
|
+
|
|
49
|
+
#: Neutral placeholder for *display* in an unconfigured deployment. It is not a
|
|
50
|
+
#: usable address and is never used to build a URL that a caller is expected to
|
|
51
|
+
#: reach - only tools that generate human-readable facts fall back to it, exactly
|
|
52
|
+
#: as they did before the domain moved here.
|
|
53
|
+
PLACEHOLDER_PUBLIC_BASE_URL = "https://<your-domain>"
|
|
54
|
+
|
|
55
|
+
#: ``KEY=VALUE`` keys understood in the environment and in the domain file.
|
|
56
|
+
_ENV_KEYS = ("MESH_PUBLIC_URL", "AGY_PUBLIC_BASE_URL")
|
|
57
|
+
|
|
58
|
+
#: Environment variables: the domain file location, and the legacy node host.
|
|
59
|
+
_DOMAIN_FILE_ENV = "MESH_DOMAIN_FILE"
|
|
60
|
+
_LEGACY_HOST_ENV = "MESH_GATEWAY"
|
|
61
|
+
|
|
62
|
+
_CONFIG_DIR_NAME = "antigravity-mesh"
|
|
63
|
+
|
|
64
|
+
_LOCK = threading.RLock()
|
|
65
|
+
_INJECTED: Optional[str] = None
|
|
66
|
+
_FILE_OVERRIDE: Optional[str] = None
|
|
67
|
+
#: (path, mtime) -> parsed values, so a per-request caller does not re-read the
|
|
68
|
+
#: file while an edit is still picked up (the mtime changes).
|
|
69
|
+
_FILE_CACHE: Dict[str, Any] = {"path": None, "mtime": None, "values": {}}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# The KEY=VALUE parser
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def read_env_file(path: str) -> Dict[str, str]:
|
|
77
|
+
"""Parse a KEY=VALUE file; tolerates a UTF-8 BOM (Windows PowerShell 5 writes one).
|
|
78
|
+
|
|
79
|
+
This is the only parser in the project: :mod:`core.agent` imports it from here
|
|
80
|
+
(and re-exports it, so callers of ``core.agent.read_env_file`` keep working).
|
|
81
|
+
"""
|
|
82
|
+
values: Dict[str, str] = {}
|
|
83
|
+
try:
|
|
84
|
+
with open(path, "r", encoding="utf-8-sig") as handle:
|
|
85
|
+
for line in handle:
|
|
86
|
+
line = line.strip()
|
|
87
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
88
|
+
continue
|
|
89
|
+
key, _, value = line.partition("=")
|
|
90
|
+
values[key.strip()] = value.strip().strip('"').strip("'")
|
|
91
|
+
except OSError:
|
|
92
|
+
pass
|
|
93
|
+
return values
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# The domain file
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def is_termux() -> bool:
|
|
101
|
+
"""True when this process runs inside Termux on Android.
|
|
102
|
+
|
|
103
|
+
Termux is not FHS-compliant: a phone app is never root, there is no ``/etc``
|
|
104
|
+
at the usual place and no host-level configuration directory to put a domain
|
|
105
|
+
file in. The question is answered *here* rather than in a new module because
|
|
106
|
+
this one must stay importable on its own - ``gateway.py`` is deployed without
|
|
107
|
+
the rest of the package - and :mod:`core.agent` asks it too instead of
|
|
108
|
+
repeating the check.
|
|
109
|
+
|
|
110
|
+
Neither signal is trusted alone: Termux exports ``TERMUX_VERSION``, its
|
|
111
|
+
``$PREFIX`` points inside ``com.termux``, and its application data directory
|
|
112
|
+
has a fixed path. A Linux host with an unrelated ``$PREFIX`` is not a phone.
|
|
113
|
+
"""
|
|
114
|
+
if os.environ.get("TERMUX_VERSION"):
|
|
115
|
+
return True
|
|
116
|
+
if "com.termux" in os.environ.get("PREFIX", ""):
|
|
117
|
+
return True
|
|
118
|
+
return os.path.isdir("/data/data/com.termux/files/usr/bin")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def default_domain_file() -> str:
|
|
122
|
+
"""Where ``domain.env`` lives when the operator did not override the path.
|
|
123
|
+
|
|
124
|
+
Linux keeps host-level configuration in ``/etc``. Windows and Termux have no
|
|
125
|
+
such place - Windows because the concept does not exist there, Termux because
|
|
126
|
+
a phone has no root and no ``/etc`` - so both use the per-user directory that
|
|
127
|
+
also holds ``agent.env``. ``install.sh`` resolves the same path for a Termux
|
|
128
|
+
install, so the installer and the node never disagree about where the domain
|
|
129
|
+
lives.
|
|
130
|
+
"""
|
|
131
|
+
if os.name == "nt" or is_termux():
|
|
132
|
+
return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIR_NAME, "domain.env")
|
|
133
|
+
return "/etc/%s/domain.env" % _CONFIG_DIR_NAME
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def domain_file() -> str:
|
|
137
|
+
"""The domain file in effect: injected path, then ``MESH_DOMAIN_FILE``, then default."""
|
|
138
|
+
with _LOCK:
|
|
139
|
+
override = _FILE_OVERRIDE
|
|
140
|
+
if override:
|
|
141
|
+
return override
|
|
142
|
+
return os.environ.get(_DOMAIN_FILE_ENV, "").strip() or default_domain_file()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _domain_file_values() -> Dict[str, str]:
|
|
146
|
+
"""Parsed domain file, cached by modification time."""
|
|
147
|
+
path = domain_file()
|
|
148
|
+
try:
|
|
149
|
+
mtime = os.stat(path).st_mtime
|
|
150
|
+
except OSError:
|
|
151
|
+
with _LOCK:
|
|
152
|
+
_FILE_CACHE.update({"path": path, "mtime": None, "values": {}})
|
|
153
|
+
return {}
|
|
154
|
+
with _LOCK:
|
|
155
|
+
if _FILE_CACHE["path"] == path and _FILE_CACHE["mtime"] == mtime:
|
|
156
|
+
return dict(_FILE_CACHE["values"])
|
|
157
|
+
values = read_env_file(path)
|
|
158
|
+
with _LOCK:
|
|
159
|
+
_FILE_CACHE.update({"path": path, "mtime": mtime, "values": values})
|
|
160
|
+
return dict(values)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _invalidate_file_cache() -> None:
|
|
164
|
+
with _LOCK:
|
|
165
|
+
_FILE_CACHE.update({"path": None, "mtime": None, "values": {}})
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# Resolution
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
def normalise_public_base_url(value: Any) -> str:
|
|
173
|
+
"""``mesh.example.com/`` and ``https://mesh.example.com/`` -> ``https://mesh.example.com``.
|
|
174
|
+
|
|
175
|
+
A bare host gains the ``https://`` scheme; a trailing slash is dropped so that
|
|
176
|
+
joining a path never produces a double slash. An explicit ``http://`` is left
|
|
177
|
+
alone - the operator may have a good reason. Public because callers that accept
|
|
178
|
+
a domain of their own (``core.mcp_tools.configure(public_url=...)``) normalise it
|
|
179
|
+
through the same rules instead of inventing their own.
|
|
180
|
+
"""
|
|
181
|
+
text = str(value or "").strip().strip('"').strip("'").rstrip("/")
|
|
182
|
+
if not text:
|
|
183
|
+
return ""
|
|
184
|
+
if not text.startswith(("http://", "https://")):
|
|
185
|
+
text = "https://" + text
|
|
186
|
+
return text.rstrip("/")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _resolve() -> Dict[str, str]:
|
|
190
|
+
"""``{"base": ..., "source": "injected|env|file|default"}`` for the current state."""
|
|
191
|
+
with _LOCK:
|
|
192
|
+
injected = _INJECTED
|
|
193
|
+
if injected:
|
|
194
|
+
return {"base": injected, "source": "injected"}
|
|
195
|
+
|
|
196
|
+
for key in _ENV_KEYS:
|
|
197
|
+
value = normalise_public_base_url(os.environ.get(key))
|
|
198
|
+
if value:
|
|
199
|
+
return {"base": value, "source": "env"}
|
|
200
|
+
|
|
201
|
+
file_values = _domain_file_values()
|
|
202
|
+
for key in _ENV_KEYS:
|
|
203
|
+
value = normalise_public_base_url(file_values.get(key))
|
|
204
|
+
if value:
|
|
205
|
+
return {"base": value, "source": "file"}
|
|
206
|
+
|
|
207
|
+
return {"base": DEFAULT_PUBLIC_BASE_URL, "source": "default"}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def public_base_url() -> str:
|
|
211
|
+
"""Public base URL, e.g. ``https://mesh.example.com`` - never with a trailing slash."""
|
|
212
|
+
return _resolve()["base"]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def public_host() -> str:
|
|
216
|
+
"""Host part of :func:`public_base_url`, e.g. ``mesh.example.com``."""
|
|
217
|
+
base = public_base_url()
|
|
218
|
+
return urlparse(base).netloc or base
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def public_url(path: str = "") -> str:
|
|
222
|
+
"""``public_base_url()`` joined with *path* (this is not gateway.public_url()).
|
|
223
|
+
|
|
224
|
+
``gateway.public_url(path, user, token)`` additionally appends the node
|
|
225
|
+
credentials; this one is the plain join that share links and log links need.
|
|
226
|
+
"""
|
|
227
|
+
base = public_base_url()
|
|
228
|
+
if not path:
|
|
229
|
+
return base
|
|
230
|
+
return base + (path if path.startswith("/") else "/" + path)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def gateway_host() -> str:
|
|
234
|
+
"""Authority a node dials for its tunnel (no scheme, unless legacy carries one).
|
|
235
|
+
|
|
236
|
+
The configured public base wins, so moving the domain into ``domain.env`` moves
|
|
237
|
+
the tunnel with it. Only when nothing is configured does the legacy
|
|
238
|
+
``MESH_GATEWAY`` decide, and a legacy value that already carries a scheme (for
|
|
239
|
+
example ``ws://10.0.0.1``) is passed through untouched, because the node's
|
|
240
|
+
tunnel builder honours such a scheme.
|
|
241
|
+
"""
|
|
242
|
+
resolved = _resolve()
|
|
243
|
+
if resolved["source"] != "default":
|
|
244
|
+
parsed = urlparse(resolved["base"])
|
|
245
|
+
authority = parsed.netloc or parsed.path
|
|
246
|
+
if parsed.netloc and parsed.path:
|
|
247
|
+
authority = parsed.netloc + parsed.path
|
|
248
|
+
return authority
|
|
249
|
+
|
|
250
|
+
legacy = str(os.environ.get(_LEGACY_HOST_ENV, "") or "").strip().rstrip("/")
|
|
251
|
+
if legacy:
|
|
252
|
+
return legacy
|
|
253
|
+
return urlparse(DEFAULT_PUBLIC_BASE_URL).netloc or DEFAULT_PUBLIC_BASE_URL
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def source() -> str:
|
|
257
|
+
"""Where the current value comes from: ``injected``, ``env``, ``file`` or ``default``."""
|
|
258
|
+
return _resolve()["source"]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def is_explicit() -> bool:
|
|
262
|
+
"""True when the domain was configured (by injection, environment or file).
|
|
263
|
+
|
|
264
|
+
Tools that must not assume a deployment - the ones that print neutral facts -
|
|
265
|
+
use this to decide between the configured domain and
|
|
266
|
+
:data:`PLACEHOLDER_PUBLIC_BASE_URL`.
|
|
267
|
+
"""
|
|
268
|
+
return source() != "default"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ---------------------------------------------------------------------------
|
|
272
|
+
# Injection (tests, and embedders that resolve the domain themselves)
|
|
273
|
+
# ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
def configure(**kwargs: Any) -> None:
|
|
276
|
+
"""Inject the domain instead of reading the environment and the file.
|
|
277
|
+
|
|
278
|
+
Recognised keys:
|
|
279
|
+
|
|
280
|
+
``public_base_url``
|
|
281
|
+
The value to use. ``None`` or ``""`` drops a previous injection, so the
|
|
282
|
+
environment and the domain file decide again.
|
|
283
|
+
``domain_file``
|
|
284
|
+
Alternative ``domain.env`` path. ``None`` or ``""`` restores the
|
|
285
|
+
environment/default location.
|
|
286
|
+
"""
|
|
287
|
+
global _INJECTED, _FILE_OVERRIDE
|
|
288
|
+
with _LOCK:
|
|
289
|
+
if "public_base_url" in kwargs:
|
|
290
|
+
_INJECTED = normalise_public_base_url(kwargs.get("public_base_url")) or None
|
|
291
|
+
if "domain_file" in kwargs:
|
|
292
|
+
value = str(kwargs.get("domain_file") or "").strip()
|
|
293
|
+
_FILE_OVERRIDE = value or None
|
|
294
|
+
_invalidate_file_cache()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def reset() -> None:
|
|
298
|
+
"""Drop injection and cached file contents (call between tests)."""
|
|
299
|
+
global _INJECTED, _FILE_OVERRIDE
|
|
300
|
+
with _LOCK:
|
|
301
|
+
_INJECTED = None
|
|
302
|
+
_FILE_OVERRIDE = None
|
|
303
|
+
_invalidate_file_cache()
|