feat(operator): Phase 6 — relay-mode read (screen) + stop-condition engine (watch)

Completes the remote-drive triad for a broker-owned sandbox the operator can't
exec into directly: keys (in) → watch (wait) → screen (out).

- _sbx:data PTY-relay frames are absorbed into a capped rolling terminal buffer
  (not surfaced as chat); `screen` returns it ansi-stripped (or raw). sandbox.
  strip_ansi handles CSI/OSC/CR noise so output greps cleanly.
- `watch` is a formal stop-condition engine: blocks until a regex matches in the
  screen buffer or chat events, or an idle-quiescence window, or a hard timeout,
  then reports which fired — the autonomy loop's principled wait.
- Skill doctrine updated with the type→wait→read relay loop.

28 offline tests green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-26 20:13:08 -07:00
parent 60215bbdd4
commit 338e843e85
5 changed files with 226 additions and 6 deletions
+18
View File
@@ -22,6 +22,24 @@ Nothing here opens a websocket or holds state; the bridge wires these in.
from __future__ import annotations
import asyncio
import re
# CSI / OSC / single-char escape sequences + carriage returns, so relayed PTY
# bytes read as plain text. We strip rather than interpret: the operator wants
# to grep the output, not render a faithful screen.
_ANSI_RE = re.compile(
r"\x1b\[[0-9;?]*[ -/]*[@-~]" # CSI … (colours, cursor moves)
r"|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)" # OSC … (title sets) BEL/ST-terminated
r"|\x1b[@-Z\\-_]" # two-char escapes
)
def strip_ansi(text: str) -> str:
"""Drop ANSI/control noise from relayed terminal output. Collapses bare
carriage returns (so progress redraws don't stack) but keeps newlines."""
clean = _ANSI_RE.sub("", text)
clean = clean.replace("\r\n", "\n").replace("\r", "\n")
return "".join(c for c in clean if c == "\n" or c == "\t" or c >= " ")
# Match the native harness so behaviour is identical across both drivers.
EXEC_TIMEOUT = 60.0 # NATIVE_TOOL_TIMEOUT