app
Google Antigravity
Public Made by Adomby adom
Google Antigravity in Adom Hydrogen: the editor extension, the agy CLI and a dock dashboard that installs both into your container.
← Commit history
Fix dashboard startup when editor discovery exceeds readiness timeout
3 files changed
+117−4
adom_antigravity.py+21−3@@ -222,6 +222,10 @@ class Handler(SimpleHTTPRequestHandler): b = r.read(); self.send_response(r.status); self.send_header('Content-Type', r.headers.get('Content-Type', 'application/json')); self.send_header('Content-Length', str(len(b))); self.end_headers(); self.wfile.write(b) def do_GET(self):+ if self.path == '/api/health':+ # Dock readiness must not wait for agy or editor port discovery.+ return self.json({'app': 'adom/antigravity', 'version': VERSION,+ 'pid': os.getpid(), 'state': 'healthy'}) if self.path in ['/api/status', '/state']: return self.json(context()) if self.path == '/events':@@ -269,6 +273,18 @@ class Handler(SimpleHTTPRequestHandler): return self.send_error(404) +def server_identity(url):+ """Cheap readiness probe; allow older servers time for their full diagnostics."""+ try:+ return json.load(urllib.request.urlopen(url + 'api/health', timeout=1))+ except urllib.error.HTTPError as e:+ if e.code != 404:+ raise+ # 0.1.5's status can take 8s for agy, 4.5s for editor ports, and 1.5s+ # for the hub. A short timeout falsely declares that live server dead.+ return json.load(urllib.request.urlopen(url + 'api/status', timeout=20))++ def main(): p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument('command', choices=['status', 'context', 'install-extension', 'install-cli', 'open', 'serve', '_serve'])@@ -286,14 +302,16 @@ def main(): ThreadingHTTPServer(('127.0.0.1', a.port), Handler).serve_forever(); return url = f'http://127.0.0.1:{a.port}/' try:- r = json.load(urllib.request.urlopen(url + 'api/status', timeout=1)); assert r.get('app') == 'adom/antigravity' and r['version'] == VERSION+ r = server_identity(url); assert r.get('app') == 'adom/antigravity' and r['version'] == VERSION except Exception: log = Path.home() / '.local/state/adom/antigravity'; log.mkdir(parents=True, exist_ok=True) with (log / 'dashboard.log').open('ab') as out:- subprocess.Popen([sys.executable, str(Path(__file__).resolve()), '_serve', '--port', str(a.port)], stdout=out, stderr=out, start_new_session=True)+ process = subprocess.Popen([sys.executable, str(Path(__file__).resolve()), '_serve', '--port', str(a.port)], stdin=subprocess.DEVNULL, stdout=out, stderr=out, start_new_session=True) for _ in range(50):+ if process.poll() is not None:+ raise RuntimeError('Dashboard process exited; inspect ' + str(log / 'dashboard.log')) try:- ready = json.load(urllib.request.urlopen(url + 'api/status', timeout=.5))+ ready = server_identity(url) if ready.get('app') != 'adom/antigravity' or ready.get('version') != VERSION: raise RuntimeError('Port is occupied by a different service') break
dockbar.json+1−1@@ -1,7 +1,7 @@ { "dock_manifest": "1.2.0", "webview": { "title": "Antigravity", "favicon": "dashboard/icon.svg" },- "health": { "url": "/api/status", "poll_seconds": 15 },+ "health": { "url": "/api/health", "poll_seconds": 15 }, "launch": { "mode": "container_command", "console": "show",
test_startup.pyadded+95@@ -0,0 +1,95 @@+"""Launch contract regressions: slow diagnostics must not break the dock."""+import json+import os+from pathlib import Path+import signal+import socket+import subprocess+import sys+import tempfile+import threading+import time+import unittest+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer+from unittest.mock import patch++import adom_antigravity as app+++class LegacyHandler(BaseHTTPRequestHandler):+ def log_message(self, *args):+ pass++ def do_GET(self):+ if self.path != '/api/status':+ self.send_error(404)+ return+ time.sleep(1.6) # ConfRoomROG; longer than both original startup budgets.+ body = json.dumps({'app': 'adom/antigravity', 'version': app.VERSION}).encode()+ self.send_response(200)+ self.send_header('Content-Length', str(len(body)))+ self.end_headers()+ self.wfile.write(body)+++class StartupTests(unittest.TestCase):+ def serve_fixture(self, handler):+ server = ThreadingHTTPServer(('127.0.0.1', 0), handler)+ thread = threading.Thread(target=server.serve_forever, daemon=True)+ thread.start()+ self.addCleanup(server.server_close)+ self.addCleanup(server.shutdown)+ return server.server_port++ def launch(self, port, home):+ return subprocess.run(+ [sys.executable, str(Path(app.__file__)), 'serve', '--print-url', '--port', str(port)],+ env={**os.environ, 'HOME': home}, capture_output=True, text=True, timeout=25)++ def test_legacy_slow_status_is_reused_without_spawning(self):+ port = self.serve_fixture(LegacyHandler)+ with tempfile.TemporaryDirectory() as home:+ result = self.launch(port, home)+ self.assertEqual(result.returncode, 0, result.stderr)+ self.assertEqual(result.stdout.strip(), f'http://127.0.0.1:{port}/')+ self.assertFalse((Path(home) / '.local/state/adom/antigravity').exists())++ def test_health_does_not_run_external_diagnostics(self):+ port = self.serve_fixture(app.Handler)+ with patch.object(app, 'context', side_effect=AssertionError('diagnostics ran')):+ self.assertEqual(app.server_identity(f'http://127.0.0.1:{port}/')['pid'], os.getpid())++ def test_cold_start_then_relaunch_keeps_same_process(self):+ with socket.socket() as sock:+ sock.bind(('127.0.0.1', 0))+ port = sock.getsockname()[1]+ with tempfile.TemporaryDirectory() as home:+ first = self.launch(port, home)+ self.assertEqual(first.returncode, 0, first.stderr)+ url = first.stdout.strip()+ pid = app.server_identity(url)['pid']+ try:+ second = self.launch(port, home)+ self.assertEqual(second.returncode, 0, second.stderr)+ self.assertEqual(second.stdout.strip(), url)+ self.assertEqual(app.server_identity(url)['pid'], pid)+ finally:+ os.kill(pid, signal.SIGTERM)++ def test_other_service_is_not_terminated(self):+ class OtherHandler(LegacyHandler):+ def do_GET(self):+ body = b'{"app":"another-app","version":"1"}'+ self.send_response(200)+ self.send_header('Content-Length', str(len(body)))+ self.end_headers()+ self.wfile.write(body)+ port = self.serve_fixture(OtherHandler)+ with tempfile.TemporaryDirectory() as home:+ result = self.launch(port, home)+ self.assertNotEqual(result.returncode, 0)+ self.assertEqual(app.server_identity(f'http://127.0.0.1:{port}/')['app'], 'another-app')+++if __name__ == '__main__':+ unittest.main()