feat(watch,serve): live watch from a phone + tunnel-edge exposure auth (0.18.0) - #293
Merged
Conversation
The computer-use loop now distinguishes two ways to hit execution.timeoutMs: - budget_reached: the wall-clock cap was reached AFTER at least one material (non-idle) action. Maps to ActorStatus "passed" — a non-failure completion for open-ended "watch it play" sessions, distinct from goal_satisfied. - timed_out: the cap was reached with ZERO material progress (hung provider, idle-only stall). Stays a failure. Adds a materialActions counter (exposed as counts.materialActions), branches both deadline sites, and handles the new reason in the exhaustive statusForCompletion switch (no default → compile-time forcing). ActorStatus is unchanged, so no provider mapper ripples. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decision A (tunnel-edge auth): serve and watch gain first-class authenticated exposure. Exposure is TUNNEL-EDGE only — ngrok --oauth google (with repeatable --allow-email/--allow-domain allow rules) or an operator --public-url they secure. One shared validateExposure() enforces a fail-closed matrix: exposure requires EITHER edge auth OR --safe (serve); watch --expose always requires edge auth (a live run is never share_ready). Removes the in-process capability-link entirely — deletes src/observer-auth.ts and the --auth/--ttl machinery (pre-1.0 breaking change). Keeps the run-library server, host allowlist, security headers, share-safety admission, and the /_humanish/api 501 seam. Decision B (live watch): runCuaBackend now wires onObserverReady so a single-lane (and app-url) computer-use watch serves the LIVE Observer with the E2B desktop stream DURING the run, mirroring the concurrent handler. The attached server comes up regardless of eventual pass/fail and survives a timed_out/failed run (serving is not gated on result.ok). serveObserver gains an `exposed` mode that enforces the same Host allowlist + security headers (shared via src/serve-http.ts), closing the DNS-rebinding gap on the live server. Composes with --expose so it is watchable from a phone behind the ngrok edge. Better Auth is intentionally NOT used here (wrong fit for an ephemeral no-DB CLI server); documented in serve.md as belonging to a future hosted control-plane. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump package version and the release-test pin, plus the source-version pins in docs/ramp/README.md, docs/goals/current.md, and (in the prior commit) docs/contracts/schemas.md. Leaves the README drawDB hero unpkg literal untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… under --safe; reserved doc domain BLOCKER (Finding 1): serveObserver in exposed mode served the whole run library. Now an exposed watch serves ONLY the attached run: /_humanish/history.json is filtered to result.run and any other run id 404s byte-identically to a nonexistent run (no cross-run access, no existence oracle). Loopback behavior is byte-identical. watch --expose --safe is now REFUSED with HUMANISH_WATCH_SAFE_NOT_APPLICABLE instead of silently ignoring the dead flag, and the exposed watch path emits the missing raw-evidence exposure warning. Finding 2: serve --expose with no --tunnel and no --public-url now fails with HUMANISH_SERVE_EXPOSE_REQUIRES_ORIGIN even under --safe (an origin-less exposed server was an unreachable loopback no-op). Finding 3: replaced the non-reserved placeholder domain observer.example.dev with the RFC-6761 reserved observer.example.com across src/, tests/, and docs/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes "watch a live persona run from your phone" real, and moves Observer exposure to tunnel-edge auth — no hand-rolled auth in humanish. Three coherent changes shipped as 0.18.0.
1. Tunnel-edge exposure auth (replaces the in-process capability link)
serveandwatchexpose the Observer through an authenticated tunnel, not an in-process secret:--oauth <provider>(google), repeatable--allow-email/--allow-domain, passed through to ngrok's edge OAuth.--public-urlstays for a bring-your-own authed edge (Cloudflare Access / Tailscale).--saferemains an orthogonalshare_ready-only content filter onserve.--safeis refused;--exposealways requires a reachable origin.--auth link|none,--ttl, the cookie/token machinery inobserver-auth.ts) is removed. Auth belongs at the edge. Better Auth is documented as the right choice for a future hosted humanish dashboard, not this ephemeral CLI server.2. Live watch from a phone
Single-lane / app-url computer-use
watchnow streams the live E2B desktop during the run (wiresonObserverReady, mirroring the concurrent handler) and keeps serving even when the run fails or times out — previously it only served a finished, successful run, so you could never watch it play.3.
budget_reachedcompletionA computer-use session that plays productively until its wall-clock cap is now recorded as completed (
budget_reached), nottimed_out=failure. A session that made zero progress then timed out is still a failure.execution.timeoutMsis a generous safety cap; the persona stops on goal.Safety / how the blocker was caught
The adversarial review found — and this PR fixes — a real ship blocker: an exposed
watchwas serving the operator's entire local run library (all prior runs' raw, unverified screenshots) to the edge, and--safewas silently ignored on that surface. Fixed by scoping an exposed watch to only the single run being watched (history filtered to that run; every other run id 404s byte-identically), rejecting--safeonwatchas inapplicable with a clear message, and adding an explicit "raw evidence is visible to edge-authed viewers" warning. Verified by a dedicated test (exposed = only the attached run reachable; loopback = full library unchanged).Core invariants preserved: runtime E2B stream URLs stay WeakMap-gated and never persisted; loopback
watch/observe/serveare byte-identical without--expose; cleanup by exact id unchanged.Verification
pnpm release:checkgreen: 1140 tests pass (public-surface scan 471 files / 2 binaries, pack dry-run good).example.com.🤖 Generated with Claude Code