Tentacle enhancements.

This commit is contained in:
Storm Dragon committed 2026-11-04 13:44:13 -05:00
1 parent 4f9b89e38b
commit 9e15f8652d
12 files changed
+619 -22

No files matched your search

+58 -3
View File
@@ -37,6 +37,56 @@ def _token(value):
return isinstance(value, str) and len(value.encode("utf-8")) == 32
def valid_remote_speech(speech):
"""Validate typed speech including its encoded IPC bound; no delivery."""
try:
_fields(speech, ("kind",), ("priority", "parts", "paused"))
priority, parts = speech.get("priority", 0), speech.get("parts", [])
if type(priority) is not int or not isinstance(parts, list):
return False
if speech["kind"] == "speak":
if not 0 <= priority <= 2 or "paused" in speech or not 1 <= len(parts) <= 128:
return False
for part in parts:
_fields(part, (), ("text", "characters", "language", "end"))
text, language = part.get("text", ""), part.get("language", "")
characters, end = part.get("characters", False), part.get("end", False)
if (not isinstance(text, str) or not isinstance(language, str)
or type(characters) is not bool or type(end) is not bool):
return False
if end:
if text or language or characters:
return False
elif (not text or len(language) > 64
or any(char not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" for char in language)):
return False
elif speech["kind"] == "cancel":
if priority or parts or "paused" in speech:
return False
elif speech["kind"] == "pause":
if priority or parts or type(speech.get("paused")) is not bool:
return False
else:
return False
# Match Go's canonical omitempty representation and JSON HTML escaping
# so both SDKs enforce the same payload bound, including escaped text.
canonical = {"kind": speech["kind"]}
if priority:
canonical["priority"] = priority
if parts:
canonical["parts"] = [{key: value for key, value in part.items() if value}
for part in parts]
if "paused" in speech:
canonical["paused"] = speech["paused"]
encoded = _encode(canonical)
for raw, escaped in ((b"<", b"\\u003c"), (b">", b"\\u003e"), (b"&", b"\\u0026"),
(b"\xe2\x80\xa8", b"\\u2028"), (b"\xe2\x80\xa9", b"\\u2029")):
encoded = encoded.replace(raw, escaped)
return len(encoded) <= 6144
except (ProtocolError, TypeError, ValueError, UnicodeError):
return False
class SessionError(Exception):
def __init__(self, code, message):
super().__init__("Tentacle session error " + str(code))
@@ -226,13 +276,13 @@ class SessionClient:
waiter.put_nowait(value)
def _event(self, event):
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason"))
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason", "speech"))
if not _positive(event["sequence"]) or not _positive(event["epoch"]):
raise ProtocolError("Invalid event sequence")
if event["kind"] == "remote.action":
grants = event.get("grants")
role = event.get("role")
if (not _token(event.get("action")) or event.get("lease", "") != "" or event.get("reason", "") != ""
if ("speech" in event or not _token(event.get("action")) or event.get("lease", "") != "" or event.get("reason", "") != ""
or role not in ("controller", "controlled") or not isinstance(grants, list)
or not grants or any(not isinstance(grant, str) for grant in grants)
or len(set(grants)) != len(grants) or not set(grants) <= REMOTE_GRANTS
@@ -240,9 +290,14 @@ class SessionClient:
or role == "controlled" and set(grants) & {"remote-present", "keyboard-capture"}):
raise ProtocolError("Invalid action event")
elif event["kind"] == "remote.revoked":
if (not _token(event.get("lease")) or event.get("action", "") != "" or event.get("role", "") != ""
if ("speech" in event or not _token(event.get("lease")) or event.get("action", "") != "" or event.get("role", "") != ""
or event.get("grants", []) != [] or event.get("reason") not in ("closed", "expired", "local", "replaced")):
raise ProtocolError("Invalid revoke event")
elif event["kind"] == "remote.output":
if (not _token(event.get("lease")) or event.get("action", "") != ""
or event.get("role", "") != "" or event.get("grants", []) != []
or event.get("reason", "") != "" or not valid_remote_speech(event.get("speech"))):
raise ProtocolError("Invalid output event")
else:
raise ProtocolError("Unknown session event")
with self._lock: