Skip to content

Commit 04a64d2

Browse files
committed
v68: add reliable mobile navigation between remote dashboard and chat
1 parent d97d089 commit 04a64d2

6 files changed

Lines changed: 118 additions & 7 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,7 @@ install `.[providers]` when using the original remote backends.
221221
## V100 campaign v28
222222

223223
Persistent goals, A/B lineages, bounded research drones, concurrent chat, private Linux desktop and pinned public benchmark gates: see [CAMPAIGN.md](CAMPAIGN.md). Optional setup stages report deferred features explicitly.
224+
225+
For private phone access, see [SYNTA_REMOTE_ACCESS.md](SYNTA_REMOTE_ACCESS.md).
226+
The remote dashboard and master chat share mobile navigation; an existing v67
227+
gateway can receive the [v68 update](SYNTA_V68.md) without restarting the mission.

‎SYNTA_V68.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Synta v68
2+
3+
The private remote dashboard and master chat share a mobile navigation bar.
4+
Dashboard link injection now handles body attributes, uppercase tags and quoted
5+
attribute values containing `>`. It preserves the host renderer, script bytes
6+
and Content Security Policy. Authentication and command routing stay unchanged.
7+
8+
For an existing v67 installation, run `tools/update-synta-navigation.sh` with
9+
the published v68 commit. It replaces the package and restarts only
10+
`synta-remote.service`. Production changes since v67 are limited to the remote
11+
gateway. The mission, Windows workers, dashboard and Tailscale Serve settings
12+
are retained. Older installations require the full retention upgrader.
13+
14+
HTTPS opens the dashboard or `/chat`. To open a shell on an iPhone, configure
15+
an SSH client with host `debian1.tail68f677.ts.net`, port `22`, username `marek`
16+
and the existing Debian SSH credentials. Keep Tailscale connected. Do not put
17+
`https://` or `/chat` in the SSH host field. Existing OpenSSH access is separate
18+
from the HTTPS gateway; no public port forwarding is required.
19+
20+
Regression coverage includes authenticated proxy navigation, unchanged script
21+
bytes and security policy, body tag variants and idempotent insertion. This
22+
environment cannot verify the operator's phone or Debian services directly.
23+
24+
Validation: 1210 tests passed, 65 skipped, one existing PEFT warning. Ruff and
25+
the targeted updater's Bash syntax check passed.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "rlms"
33
authors = [
44
{name = "Alex Zhang", email = "altzhang@mit.edu"},
55
]
6-
version = "0.1.3+v100.67"
6+
version = "0.1.3+v100.68"
77
description = "Recursive Language Models."
88
readme = "README.md"
99
license = "MIT"

‎rlm/v100/remote_access.py‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616
from urllib.parse import urlsplit
1717
from urllib.request import Request, urlopen
1818

19+
NAVIGATION = b"""<nav id="synta-remote-navigation" aria-label="Synta navigation" style="display:flex;flex-wrap:wrap;gap:12px;margin:0 0 20px;padding:8px;border:1px solid #536b88;border-radius:10px;background:#101725;font:16px system-ui"><a href="/" style="display:flex;align-items:center;min-height:44px;padding:0 16px;color:#9bcaff">Dashboard</a><a href="/chat" style="display:flex;align-items:center;min-height:44px;padding:0 16px;color:#9bcaff">Master chat</a></nav>"""
20+
21+
22+
def dashboard_navigation(body: bytes) -> bytes:
23+
"""Add gateway-owned navigation without modifying the renderer or its script."""
24+
if b'id="synta-remote-navigation"' in body:
25+
return body
26+
opening = re.search(rb"""<body\b(?:[^>"']|"[^"]*"|'[^']*')*>""", body, re.IGNORECASE)
27+
if opening is None:
28+
raise ValueError("Dashboard HTML has no body element for remote navigation")
29+
return body[: opening.end()] + NAVIGATION + body[opening.end() :]
30+
31+
1932
CHAT_SCRIPT = r"""
2033
const rows=new Map();let busy=false,csrf='';
2134
async function api(path,options={}){const abort=new AbortController(),timer=setTimeout(()=>abort.abort(),8000);try{const r=await fetch(path,{cache:'no-store',signal:abort.signal,...options});if(!r.ok)throw Error('HTTP '+r.status);return await r.json()}finally{clearTimeout(timer)}}
@@ -28,7 +41,9 @@
2841
CHAT_PAGE = (
2942
"""<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Synta remote chat</title><style>
3043
body{background:#101725;color:#e1e9f6;font:16px system-ui;margin:0;padding:20px;max-width:960px;margin:auto}a{color:#9bcaff}nav{display:flex;gap:22px}article{background:#1a2538;border:1px solid #33475f;border-radius:12px;margin:16px 0;padding:16px}h3{font-size:12px;overflow-wrap:anywhere;color:#9cb0c9}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;line-height:1.6}textarea{box-sizing:border-box;width:100%;min-height:120px;background:#1a2538;color:#fff;padding:14px;font:inherit;border:1px solid #536b88;border-radius:8px}button{padding:12px 26px;background:#78b4ff;border:0;border-radius:8px;font:inherit;cursor:pointer}summary{cursor:pointer;color:#a6cfff}#composer{position:sticky;bottom:0;background:#101725;padding:12px 0}#status{color:#afc7e5}
31-
</style><nav><a href="/">Dashboard</a><a href="/chat">Master chat</a></nav><h1>Synta master chat</h1><p>Authenticated owner commands use the same mission queue. Replies and execution receipts stay available after you leave.</p><p id="status">Connecting</p><main id="history"></main><section id="composer"><textarea id="message" maxlength="8000" placeholder="Message to Synta, /status, /goal ..."></textarea><button id="send">Send</button></section><script>"""
44+
</style>"""
45+
+ NAVIGATION.decode()
46+
+ """<h1>Synta master chat</h1><p>Authenticated owner commands use the same mission queue. Replies and execution receipts stay available after you leave.</p><p id="status">Connecting</p><main id="history"></main><section id="composer"><textarea id="message" maxlength="8000" placeholder="Message to Synta, /status, /goal ..."></textarea><button id="send">Send</button></section><script>"""
3247
+ CHAT_SCRIPT
3348
+ "</script></html>"
3449
)
@@ -181,11 +196,7 @@ def do_GET(self): # noqa: N802
181196
if len(body) > 8 * 2**20:
182197
raise ValueError("Dashboard response exceeds 8 MiB")
183198
if route == "/":
184-
body = body.replace(
185-
b"<body>",
186-
b'<body><nav><a href="/chat">Open Synta master chat</a></nav>',
187-
1,
188-
)
199+
body = dashboard_navigation(body)
189200
self.reply(
190201
body,
191202
response.headers.get("Content-Type", "application/octet-stream"),

‎tests/test_v100_remote_access.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import threading
33
from contextlib import contextmanager
4+
from io import BytesIO
45
from urllib.error import HTTPError
56
from urllib.request import Request, urlopen
67

@@ -52,6 +53,7 @@ def test_owner_chat_csrf_exact_origin_queue_and_persistent_reply(tmp_path):
5253
with gateway(tmp_path) as (server, base):
5354
code, headers, body = request(base, "/chat")
5455
assert code == 200 and b"Awaiting saved result" in body
56+
assert remote_access.NAVIGATION in body
5557
assert "script-src 'sha256-" in headers["Content-Security-Policy"]
5658
assert server.server_address[0] == "127.0.0.1"
5759
_, _, body = request(base, "/api/chat")
@@ -90,6 +92,52 @@ def test_owner_chat_csrf_exact_origin_queue_and_persistent_reply(tmp_path):
9092
assert json.loads(body)["requests"][0]["response"] == reply
9193

9294

95+
@pytest.mark.parametrize(
96+
"opening",
97+
[
98+
b"<body>",
99+
b'<body class="dashboard">',
100+
b"<BODY class='dashboard'>",
101+
b'<body data-label="a > b">',
102+
],
103+
)
104+
def test_dashboard_navigation_preserves_renderer_and_body_attributes(opening):
105+
source = b"<!doctype html><html>" + opening + b"<script>render()</script></body></html>"
106+
result = remote_access.dashboard_navigation(source)
107+
assert opening + remote_access.NAVIGATION in result
108+
assert result.replace(remote_access.NAVIGATION, b"", 1) == source
109+
assert remote_access.dashboard_navigation(result) == result
110+
111+
112+
def test_dashboard_without_body_cannot_silently_omit_navigation():
113+
with pytest.raises(ValueError, match="no body element"):
114+
remote_access.dashboard_navigation(b"<html><p>Missing body</p></html>")
115+
116+
117+
def test_gateway_dashboard_injects_mobile_links_and_preserves_upstream_policy(
118+
tmp_path, monkeypatch
119+
):
120+
source = b'<html><body class="dashboard"><script>render()</script></body></html>'
121+
policy = "default-src 'none'; script-src 'sha256-existing'; style-src 'unsafe-inline'"
122+
calls = []
123+
124+
def upstream(req, timeout):
125+
calls.append((req.full_url, timeout))
126+
response = BytesIO(source)
127+
response.headers = {"Content-Type": "text/html", "Content-Security-Policy": policy}
128+
return response
129+
130+
monkeypatch.setattr(remote_access, "urlopen", upstream)
131+
with gateway(tmp_path) as (_, base):
132+
code, headers, body = request(base, "/")
133+
assert code == 200
134+
assert headers["Content-Security-Policy"] == policy
135+
assert body.replace(remote_access.NAVIGATION, b"", 1) == source
136+
assert b'href="/chat"' in body and b'href="/"' in body
137+
assert b"min-height:44px" in body
138+
assert calls == [(CONFIG["dashboard"] + "/", 5)]
139+
140+
93141
@pytest.mark.parametrize(
94142
"route",
95143
[

‎tools/update-synta-navigation.sh‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
# v67 -> v68: production changes are limited to the remote gateway.
3+
set -euo pipefail
4+
REV="${1:?Pass the pinned 40-character v68 commit}"
5+
[[ "$REV" =~ ^[0-9a-f]{40}$ ]] || { echo 'Invalid revision' >&2; exit 1; }
6+
source "$HOME/ai-v100/env.sh"
7+
PY="$AI_V100_ROOT/venvs/v100-continual/bin/python"
8+
"$PY" <<'PY'
9+
from importlib.metadata import version
10+
11+
installed = version('rlms')
12+
if installed not in ('0.1.3+v100.67', '0.1.3+v100.68'):
13+
raise SystemExit(f'Targeted navigation update requires v67 or v68; installed: {installed}. Use the full retention upgrader for older versions.')
14+
PY
15+
systemctl --user is-active --quiet synta-remote.service
16+
export UV_HTTP_TIMEOUT=600 UV_HTTP_RETRIES=5
17+
uv --no-config pip install --python "$PY" --no-deps --reinstall-package rlms \
18+
"rlms @ git+https://github.com/Mantiee/rlm.git@$REV"
19+
uv --no-config pip check --python "$PY"
20+
systemctl --user restart synta-remote.service
21+
systemctl --user is-active --quiet synta-remote.service
22+
echo 'Remote gateway updated. Mission, dashboard, Tailscale routing and Windows tasks were not restarted.'
23+
echo 'Reload the Tailscale HTTPS dashboard to see Dashboard / Master chat navigation.'

0 commit comments

Comments
 (0)