Repository navigation
Live screen streams of leased devices #593
Description
Request: none
Delivery: one PR, #576 (branch feature/stream-poc), one review over the whole diff, no task split: the maintainer's choice, 2026-10-15. File and line references below are at f68c1c5f unless marked (main).
Where it stands: unit and console-lane tests pass; the single-worker console check on real devices passed on 2026-10-15 (iPhone 17 simulator, iOS 27; Pixel 9 emulator); an in-process gateway and worker stream through each other in the fast lane (F30); the slow-lane tests and the gateway check on real devices have not passed yet. No slow-lane test covers a gateway.
Problem
An operator cannot see what is on a leased device. An agent drives a simulator or an emulator on a worker, maybe a machine in another room, behind a gateway. When the agent hangs, loops or fails a test, the only way to see the screen is to log in to that machine and find the right window. Nothing in Simlock shows a picture.
Who it is for
- An operator watching agents' devices in the console, on any lease in the fleet.
- An agent or a tool that holds a lease and wants a live picture of its own device over the HTTP API, for example to record a failing run.
Outcome
- In the console, a lease's page has a Screen panel. Pressing Show screen shows the device's screen live, with its size, rate and bandwidth over the last two seconds. Pressing Stop, leaving the page or closing the tab stops it.
- Over HTTP, one
GETon a lease's stream URL returns the live picture for as long as the connection stays open. - This works for leases on iOS simulators and on Android emulators, on a single worker and through a gateway, with the same URL and the same bytes.
- Nothing is captured while nobody looks. A capture never outlives its viewer or its lease, and a broken capture ends instead of freezing, with two accepted exceptions: after Simlock reboots a crashed iOS simulator under its lease, the viewer can see the old picture until Stop and Show screen (F31); and an Android open still pushing (≤ 30 s) or starting (≤ 15 s) when
lease.endWaitMsruns out can overlap the reclaim. A still screen stays live.
How it works for the user
sequenceDiagram
participant U as Operator (console)
participant G as Gateway
participant W as Worker
participant D as Device
U->>G: Show screen (GET /v1/stream/leases/L1)
G->>G: lease L1 known, caller may see it?
alt unknown, or another agent's lease
G-->>U: 404 UNKNOWN_LEASE
else worker disconnected
G-->>U: 503 WORKER_UNREACHABLE
end
G->>W: relay to the worker that holds L1
W->>W: lease still held by that requester?
W->>D: start capture
alt physical device
W-->>U: 415 STREAM_NOT_SUPPORTED
else capture program missing, or no picture in 30 s
W-->>U: 503 STREAM_UNAVAILABLE
end
W-->>U: 200, picture headers, then frames
U->>G: Stop / leave page (request closed)
G->>W: relay cancelled
W->>D: capture stopped
Note over W,D: the lease ends while watched: capture stopped, the stream ends
Before (any daemon today):
$ curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
http://gw:7777/v1/stream/leases/L1
404
After (stream.enabled: true on the gateway and the worker):
$ curl -sD - -o screen.bin -H "Authorization: Bearer $TOKEN" \
http://gw:7777/v1/stream/leases/L1
HTTP/1.1 200 OK
cache-control: no-store
content-type: application/vnd.simlock.stream+h264
simlock-stream-codec: h264
simlock-stream-fps: 30
simlock-stream-height: 720
simlock-stream-width: 330
x-simlock-lease: L1
^C # closing the request stops the capture on the worker
$ curl -s -H "Authorization: Bearer $TOKEN" http://gw:7777/v1/stream/leases/L9
{"error":{"code":"STREAM_NOT_SUPPORTED","message":"A physical iPhone's screen cannot be streamed; only a simulator's can"}}
Examples
| Request | Today | After |
|---|---|---|
GET own lease, iOS simulator |
404, no such route | 200, picture |
GET own lease, Android emulator |
404, no such route | 200, picture |
GET own lease, physical iPhone or Android phone |
404, no such route | 415 STREAM_NOT_SUPPORTED |
GET another agent's lease, agent token |
404, no such route | 404 UNKNOWN_LEASE, same as unknown |
GET any lease, operator token |
404, no such route | 200, picture |
GET a lease id that does not exist, or ended |
404, no such route | 404 UNKNOWN_LEASE |
GET with an id that is not URL-decodable (%E0%A4%A) |
404, no such route | 404 UNKNOWN_LEASE |
| The lease ends before the first frame | 404, no such route | 404 UNKNOWN_LEASE |
POST to the stream URL |
404, no such route | 405 BAD_REQUEST |
GET with stream.enabled: false |
404, no such route | 404, no such route (unchanged) |
GET on an iOS worker built without the capture program |
404, no such route | 503 STREAM_UNAVAILABLE |
| The device paints nothing, iOS | 404, no such route | 503 STREAM_UNAVAILABLE after 30 s |
| The device paints nothing, Android | 404, no such route | 200 (the size is known), then the body errors after 30 s |
| Gateway: the lease's worker has streaming off | 404, no such route | 503 STREAM_UNAVAILABLE, naming the worker |
| Gateway: the lease's worker is disconnected | 404, no such route | 503 WORKER_UNREACHABLE |
Gateway: the worker answers 2xx with no simlock-stream-codec header or no body |
404, no such route | 502, the worker's code if shaped like ours, else STREAM_UNAVAILABLE |
What could go wrong
- A picture freezes and looks live. Unlikely: a stream that goes 30 s without a byte ends, and a still Android screen sends a small "alive" frame every 5 s. One known case: after Simlock reboots a crashed iOS simulator under its lease, the viewer can see the old picture until Stop and Show screen.
- A capture keeps running after its lease ends, on a device someone else now holds. Unlikely: the lease end stops it before the device is reclaimed.
- An agent sees another agent's screen. Unlikely: ownership is checked on the gateway and again on the worker.
- An Android stream is slow or choppy (about 21 fps while scrolling, measured under heavy host load). Likely on busy machines.
- A worker's CPU rises while a screen is watched: one capture per viewer.
Words used
- stream: one open
GET /v1/stream/leases/{leaseId}and the picture it carries, e.g. the console's Screen panel while it is on. - capture: what films one device for one stream, e.g. one
simlock-screen-captureprocess per viewer of a simulator, or one scrcpy server inside an emulator. - envelope: the body's framing: 4-byte big-endian length (counting the tag), 1-byte tag, payload. Tags:
0x05JSON meta (always first),0x01codec description (avcC),0x02key frame,0x03delta frame,0x04JPEG still,0x06alive (empty). A reader skips a tag it does not know. - alive frame: an empty frame a capture sends when the screen has not changed, so a still screen is not mistaken for a dead capture.
- key frame: a frame a decoder can start from; a delta frame needs the frames before it.
- relay target: a name a gateway sends a relayed HTTP exchange to on a worker, e.g.
agent-device, and nowstream. - capability: an optional module a worker reports with a state, e.g.
stream: available(no reason) orstream: unavailable (ios: No screen capture program at …). A worker with streaming off reports nostreamrow. - scrcpy server: the small Android program from the scrcpy project that encodes an Android screen on the device; Simlock carries it unchanged.
- smart socket: adb's own way to reach a socket inside a device through the adb server (
host:transport:<serial>, thenlocalabstract:<name>), with no host port opened.
Non-goals
- Input. No touches, keys or text through the stream; v1 is view-only.
- Physical devices, iOS and Android. Refused in v1.
- Audio.
- Recording or saving streams on the worker, and any history of who watched.
- A CLI command. The console and the HTTP API only.
- Choosing a device by whether it can be streamed (
requires), and showing "being watched" anywhere. - Publishing Simlock or the iOS capture program as a prebuilt package.
- A GPU against software encoder measurement for Android emulators.
Completion conditions
- With
stream.enabled: true, an operator presses Show screen on a leased iOS simulator in the console, on a single worker, and sees the screen move as an agent drives it. Manual check by the maintainer; the PR's Done when gives the date and devices (done 2026-10-15, iPhone 17 / iOS 27). - The same on a leased Android emulator. Manual check by the maintainer (done 2026-10-15, Pixel 9).
- The same through a gateway, for both platforms. Manual check by the maintainer on real devices, shown with date and devices in the PR's Done when (not done yet; no slow-lane test covers a gateway). The maintainer accepted this as a manual check on 2026-10-15.
GET /v1/stream/leases/{leaseId}answers each row of Examples with the status and code shown, on a worker and through a gateway.- Closing the request ends the capture on the worker: no capture process, and on Android no scrcpy
app_processand nosimlock-scrcpy-*.jarin/data/local/tmp. Real devices:e2e/slow-ios-stream.test.ts,e2e/slow-android-stream.test.ts. - Releasing a lease while it is watched ends the stream, and leaves the same nothing behind. Real devices: the same two tests.
- A still screen keeps its stream open past the 30 s silence bound. Real devices:
e2e/slow-android-stream.test.ts(45 s, analiveframe required),e2e/slow-ios-stream.test.ts(35 s, frames required). - With
stream.enabled: false, no stream route exists and the worker reports nostreamcapability. pnpm buildcopies the scrcpy server after checking its pinned SHA-256 (a mismatch fails the build), and on macOS with Xcode builds the iOS capture program; nothing is downloaded to stream.- The capture tool and the scrcpy server sit under
vendor/with their license texts and aNOTICE.mdeach, and the check that enforces it passes. - The slow-lane tests
e2e/slow-ios-stream.test.tsande2e/slow-android-stream.test.tspass. - A lease that ends while a capture is still opening stops that open, and its reclaim waits for the open to settle and what it started to close, up to
lease.endWaitMs(default 600000 ms). Past that the wipe starts anyway, and an Android open still pushing (≤ 30 s) or starting its server (≤ 15 s) can overlap the reclaim; accepted, with that bound.
Open questions
None.
Decisions
- ADR 0031 — A screen stream is an optional module, and a driver films the device: the route, the envelope, close-is-stop, stoppable opens, the driver capability, both platforms, the relay and its idle bound, no limit on captures per lease in v1, no business events, vendored code. Accepted — not yet implemented, PR docs(adr): 0031 a screen stream is an optional module, and a driver films the device #594.
- One PR and one review (feat(stream): a leased simulator's or emulator's screen, streamed to whoever is looking at it #576), no task split: the maintainer's choice, 2026-10-15.
- Known gaps accepted for v1: an iOS picture can freeze after a reboot under its lease until Stop and Show screen (F31); an Android open still running when
lease.endWaitMsruns out can overlap the reclaim by up to 30 s pushing or 15 s starting. - The gateway on real devices stays a manual maintainer check (accepted 2026-10-15).
- ADR 0024 — Leasing calls lease-end participants before reclaim: how a lease end stops captures.
- ADR 0026, ADR 0028 — the relay over the uplink and its byte channels; ADR 0031 amends 0028 §4: the 330 s deadline covers the channel and the response head, then a 330 s idle timer bounds every relayed body.
- ADR 0030 (docs(adr): 0030 optional modules share one contract and load only when turned on #592, not merged) is not a dependency:
daemon/main.tswires the module's parts as it does agent-device's; if 0030 is accepted the module moves onto its contract.
Technical spec
Facts
| # | Fact | Source |
|---|---|---|
| F1 | Leasing calls each LeaseEndParticipant.leaseEnded(ended, signal) after a lease end commits, before reclaim, and cuts the wait at its ceiling; a graceful stop or nuke cuts it short. |
src/leasing/lease-release-coordinator.ts:202, :375-402 |
| F2 | Driver.screenCapture?; ScreenCapture { unavailable(); open(device, settings, signal) }, ScreenFeed { body; close() }, the tag table (seed 0x04, meta 0x05, alive 0x06) and the frame encoder are in core. |
src/core/driver.ts:360; src/core/screen.ts:6-15, :35-54, :70-80 |
| F3 | DriverCatalog.openScreen throws ScreenNotSupportedError for a driver with no screenCapture and passes the signal on; screenAvailability() lists each capturing driver's reason. |
src/core/driver-catalog.ts:96-107, :109 |
| F4 | daemon/main.ts builds the worker stream only when stream.enabled, and adds its lease-end participant, capability source, relay target and mount; the gateway gets a mount only. |
src/daemon/main.ts:365-377, :388-391, :507, :546, :863-882 |
| F5 | The worker registers a capture before opening, passes AbortSignal.any([request, lease end]) to the driver, abandons the slot when the open fails, and re-reads the lease after the meta frame. |
src/stream/worker-link.ts:50-54, :63, :69, :76-84 |
| F6 | A slot settles when its open attaches or gives up. A viewer leaving (release) keeps the slot registered until its open settles and what it attached is closed; a failed open abandons it. The lease end aborts the slots, waits for each to settle, closes what attached, and stops at leasing's signal. |
src/stream/captures.ts:30, :40-48, :77-91, :106-120 |
| F7 | The meta frame within 30 s, then at most 30 s between bytes, both on the Clock port. | src/stream/feed.ts:19, :23, :63-88 |
| F8 | A path that does not decode is 404 UNKNOWN_LEASE; any method but GET is 405 BAD_REQUEST; an error that is not a stream refusal is 500 INTERNAL. |
src/stream/service.ts:19-27, :37-43, :49-56 |
| F9 | The gateway relays with requesterPrefix + the lease index's requester and the worker's lease id; RELAY_TARGET_MISSING → 503 STREAM_UNAVAILABLE; another shaped code → 503 with it; no code → rethrown (500). |
src/stream/gateway-link.ts:50-65, :73-96 |
| F10 | A refusal body is read to 16 KiB; a code is forwarded only if it matches ^[A-Z][A-Z0-9_]{0,63}$; a 2xx with no codec header or no body is 502. |
src/stream/gateway-link.ts:100, :111; src/stream/refusal.ts:41, :55 |
| F11 | Picture headers and meta frames pass one check: codec h264 or mjpeg, width and height 1–16384, fps 0–240. |
src/stream/framing.ts:7-8, :58-73 |
| F12 | The relay's 330 s deadline is cancelled at the head; a 330 s idle timer restarts on every chunk and fails WORKER_UNREACHABLE. Both timers run on the daemon's Clock (GatewayRelayOptions.clock). |
src/gateway/relay.ts:16, :27, :31, :95, :213-216, :270-284; src/daemon/main.ts:847 |
| F13 | Capability: available with no reason when any platform can capture; else unavailable with each platform's reason. |
src/stream/create.ts:86-97 |
| F14 | iOS runs <program> <udid> --codec avcc --fps --bitrate --max-dimension with SIMLOCK_IOS_DEVICE_SET; refuses physical; refuses a stopped open; close is stdin, 2 s, SIGKILL. |
src/drivers/ios/screen-capture.ts:24, :40-50, :61-75, :130-136 |
| F15 | The iOS tool paints at least every 200 ms when idle, and writes its meta with its first captured frame. | vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift:44; .../main.swift:290-300 |
| F16 | Android: random 31-bit scid, jar at /data/local/tmp/simlock-scrcpy-<scid>.jar; push ≤ 30 s, then ≤ 15 s to the size packet; stop checked before and after the push and between connection attempts; options incl. tunnel_forward=true, control=false, cleanup=true; close is socket, 1 s, SIGKILL of the shell, rm -f, ≤ 5 s. |
src/drivers/android/screen/session.ts:31-45, :88, :123, :127-157, :254-299, :203-224, :418-437 |
| F17 | Android reaches the server by host:transport:<serial> then localabstract:scrcpy_<scid> on Simlock's adb server; no adb forward. |
src/drivers/android/screen/adb-socket.ts:76, :82 |
| F18 | scrcpy 5.0.1 asks the encoder to repeat the previous frame after 100 ms (KEY_REPEAT_PREVIOUS_FRAME_AFTER), which Android's MediaFormat repeats once; a still screen then sends nothing. |
scrcpy v5.0.1 server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java:32, :322; Android MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER reference |
| F19 | Android sends alive after 5 s of quiet, only after its first key frame. |
src/drivers/android/screen/session.ts:42-45, :374 |
| F20 | With cleanup=true in streaming mode, the server starts a clean-up process, which unlinks the jar named by the first classpath entry as it starts. |
scrcpy v5.0.1 Server.java:40-46, :90-91; CleanUp.java:175-195 |
| F21 | When the host-side adb shell ends, the server on the device ends and leaves no jar. |
2026-10-15, after the host adb shell (pid 74444) exited with the daemon: adb -s emulator-5586 shell 'ps -A -o PID,STIME,ARGS | grep [s]crcpy; ls /data/local/tmp' printed only simlock-mark.json |
| F22 | pnpm build copies the scrcpy jar after its SHA-256 check (mismatch exits 1) and, on macOS, builds the iOS tool into dist/bin/; otherwise it skips it. The daemon looks there. |
scripts/build-capture.mjs:16-30, :32-55; src/daemon/main.ts:1068-1074 |
| F23 | Config: stream.enabled, stream.fps 1–60, stream.maxDimension 96–4096, stream.bitrateKbps 100–40000; drivers.ios.screenCaptureCommand overrides the iOS program. |
src/core/config.ts:950-955; src/drivers/ios/index.ts:1663-1666 |
| F24 | configSchema gains an optional stream; the protocol goes 36 → 37. |
src/contract/schemas.ts:907-916; src/contract/protocol.ts:13 |
| F25 | The console's rate and bandwidth cover the last 2 s; its decoder drops deltas over a queue of 8 and waits for a key frame; its tag table equals core's; its 415 message names physical phones. | ui/src/screen/stream.ts:36, :194, :407; ui/src/screen/stream.test.ts:617; ui/src/screen/viewer.tsx:243-244 |
| F26 | Daemon stop stops the HTTP front first, which ends every request: each viewer's abort releases its capture. Then it disposes the drivers, with no stream hook; Android's dispose stops Simlock's adb server. On Android the host adb shell ends with the daemon, which ends the device server (F21). |
src/daemon/main.ts:593-598, :564-573; src/drivers/AGENTS.md (dispose row) |
| F27 | device.exec emits no event, on purpose; both EVENTS.md files say a stream emits none either. |
docs/internal/EVENTS.md:212-225; docs/EVENTS.md:139-140 |
| F28 | A test fails a vendor/ directory with no NOTICE.md or no license text. |
src/source-hygiene.test.ts:188-215 |
| F29 | iOS, from the #576 PoC (not re-measured): 18.6–23.2 fps on swipes, 880–1097 kbit/s at 720 px, 66–78 MB RSS; the relay costs nothing measurable. | #576 PoC body, "Measured" table |
| F30 | A real gateway and worker in one process (fake driver screen): a stream relayed through the gateway (200, headers, meta); the lease released on the gateway stops the capture on the worker; a viewer leaving the gateway stops the capture on the worker; a worker with streaming off answers 503 STREAM_UNAVAILABLE naming it. |
src/daemon/stream-gateway.e2e.test.ts:150-151, :176-177, :192-193 |
| F31 | Known gap, decided for v1: the iOS capture program checks that the simulator is Booted only at start and then repaints its last surface every 200 ms, so after a reboot under the lease the viewer can see the old picture until Stop and Show screen. | vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift:63, :44; src/drivers/ios/AGENTS.md:125-128 |
Modules touched
flowchart LR
MAIN["daemon/main.ts *"] --> ST["stream/ (new) *"]
ST --> LP["leasing: LeaseEndParticipant"]
ST --> CAT["core: DriverCatalog, screen.ts, config.ts *"]
IOS["drivers/ios *"] --> CAT
AND["drivers/android/screen *"] --> CAT
IOS --> P["ports: BytePipeLauncher, TcpStreamConnector *"]
AND --> P
ST -. gateway relay .-> GW["gateway/relay.ts *"]
UI["ui/src/screen, views/leases.tsx *"] -. HTTP .-> ST
(* = new or changed.) The gateway sequence is ADR 0031 §7's diagram. Also changed: src/contract (schema, protocol), src/http/errors.ts (405, 415, 502), src/drivers/android/adb-registrar.ts and adb-host-protocol.ts (shared framing), src/drivers/android/index.ts (wires screenCapture), src/drivers/android/screen/AGENTS.md (new manual), .dependency-cruiser.mjs (not-stream, stream-not-edges), package.json (build:capture), scripts/build-capture.mjs, vendor/, e2e/fake-driver.
Surface and extension points
src/stream/index.ts:createWorkerStream→{ mount, relayTarget, relayTargetName, leaseEnd, capabilitySource };createGatewayStream→{ mount, relayTargetName }.src/core/screen.ts:ScreenCapture,ScreenFeed,ScreenNotSupportedError(415),ScreenUnavailableError(503),SCREEN_FRAME_TAG,encodeScreenFrame,encodeScreenMeta. Two implementations exist (iOS, Android); the registry isDriverCatalog.src/ports:BytePipeLauncher(binary stdout, stdin as liveness) andTcpStreamConnector, each with a Node adapter and a fake.
Contract and event changes
- No new contract operation.
configSchemagains an optionalstreamobject: protocol +1 (36 → 37, F24). - New HTTP route
GET /v1/stream/leases/{leaseId}; statuses 405, 415, 502 joinHttpStatus; codesSTREAM_NOT_SUPPORTED(415) andSTREAM_UNAVAILABLE(503). - Events: none. Both
EVENTS.mdfiles say a stream emits no event and why (F27).
Other code on the same state
- Leasing's lease end and reclaim (F1, F6): the participant stops opens in progress, waits for them to settle, closes every capture of that lease and waits, bounded by the driver's bounds and leasing's signal. Lease end wins. A nuke or daemon shutdown cuts the wait short, so its wipe may run while a capture closes: accepted, maintenance is an operator's reset.
- The health monitor rebooting a leased device: on Android the stream ends when the device socket closes or at the 30 s silence bound, and close cleans up against the serial stored at open. On iOS the viewer can see the old picture until Stop and Show screen (F31); the lease end stops it as usual.
- The Android driver's private adb server: the capture's
push,shelland smart socket go through it, scoped to the emulator's serial. Daemon stop ends every viewer's request first, then disposes it (F26). - The iOS driver's shutdown and erase: the lease end closes the capture before reclaim (F6); a simulator shut down under a live capture is the reboot case above (F31).
- A daemon stopped or killed: the iOS program sees its stdin close and stops; on Android the host
adb shellends with the daemon, which ends the device server (F21, F26). - agent-device's relay target: a second name in the same table. The relay's new idle bound applies to it too (F12).
- The link's channels: a stream holds one channel for its life; at 64 open channels a new relay waits.
Rules in play
- architecture.md 1–3, 9, 11, 12, 15, 18–21: the core names no platform; ports for spawn, timers and sockets (the relay's idle timer on the Clock port, F12); every wait bounded; every capture ends in one state.
- safety.md 4, 9, 10: nothing downloaded; Simlock's own adb server for emulators; worker answers and headers are claims.
- events.md: no events, and the catalogue says so.
- vendor-and-third-party-code.md: lands with this feature.
- documentation.md:
docs/HTTP-API.md,docs/CONFIGURATION.md,docs/CONSOLE.md, and thesrc/stream/AGENTS.mdmanual.
Tests
Seam: the stream links over a test registry and scripted feeds (src/stream/worker-link.test.ts), the gateway link over a fake fleet (src/stream/gateway-link.test.ts), drivers' screen code over fake ports (src/drivers/android/screen/session.test.ts, src/drivers/ios/screen-capture.test.ts), the console lane (e2e/console/screen.spec.ts), the slow lane on real devices.
- each row of Examples answers the status and code shown, on a worker and through a gateway.
- a gateway answers a disconnected worker 503
WORKER_UNREACHABLE, and a worker with nostreamrelay target 503STREAM_UNAVAILABLEnaming the worker. - a lease that ends while its capture is starting tells the driver to stop opening, waits for the open to settle, closes what attached, and answers
UNKNOWN_LEASE(worker-link.test.ts,captures.test.ts). - a request aborted while the driver is still opening closes the capture as soon as it attaches.
- a lease that ends after the viewer left, while the open is still running, still waits for that open (
worker-link.test.ts). - through a real in-process gateway and worker: a relayed stream answers 200 with headers and meta; releasing the lease on the gateway stops the capture on the worker; "a viewer leaving the gateway stops the capture on the worker"; a worker that does not stream answers 503
STREAM_UNAVAILABLEnaming it (src/daemon/stream-gateway.e2e.test.ts). - two viewers of one lease get two captures; one leaving keeps the other; the lease ending stops both (
worker-link.test.ts). - a lease that ends while watched ends the stream and closes the driver's screen; the participant returns only after the close, or at leasing's signal.
- a capture with no meta frame in 30 s, or silent 30 s after one, ends with an error and is closed, on a fake clock.
- the response carries the picture in its headers (
src/stream/service.test.ts). - a relayed body that keeps sending outlives the 330 s deadline; one silent for the idle time ends
WORKER_UNREACHABLEwithrelay.cancel; a head that does not arrive fails at the deadline. - a worker refusal body is read to 16 KiB only; a code that does not match the shape reaches the caller as
STREAM_UNAVAILABLE; a 2xx without a picture is 502; a relay that cannot reach the worker is 503 with the relay's code, and a worker with no stream is 503STREAM_UNAVAILABLEnaming it (gateway-link.test.ts). - a relayed picture whose codec or size fails
checkedMetais refusedSTREAM_UNAVAILABLE. - streaming on,
simlock worker list --jsonshowsstreamavailable("shows nothing and starts no capture until asked"); off, it shows nostreamcapability and the route answers 404 ("a worker with streaming off refuses the picture and says what to do"), both ine2e/console/screen.spec.ts. - the capability is
availablewith no reason when any platform can capture, elseunavailablewith each platform's reason (create.test.ts). pnpm buildfails, copying nothing, on a scrcpy jar that misses its pin, and copies one that matches byte for byte (src/build-capture.test.ts).- iOS: the program runs on the UDID inside Simlock's device set with the settings; physical is not supported; a missing program is unavailable; a stopped open starts nothing; close stops it through stdin, then
SIGKILLafter 2 s. - Android: per-session
scid, jar path and socket; the device socket reached byhost:transportandlocalabstractwith noadb forward; the socket read only when pulled, every delta delivered;aliveon a still screen, and none before the first picture; an open stopped during the push starts no server and removes the jar; physical not supported; close ≤ 5 s kills the shell and removes the jar. - the console's tag table equals core's, and its rate covers the last two seconds.
- console lane (
e2e/console/screen.spec.ts): "shows nothing and starts no capture until asked"; "Show screen starts the capture, paints it, and reports what it is showing"; "Stop stops the capture, and so does leaving the page"; "a worker with streaming off refuses the picture and says what to do"; "a release takes the pane away and leaves no capture running". - slow lane:
e2e/slow-ios-stream.test.tsstreams meta, anavcCdescription and a key frame, asserts the stream of a still simulator is still open at 35 s, and a viewer leaving and a lease released under a viewer each stop the capture;e2e/slow-android-stream.test.tsdoes the same, holds a still screen 45 s withaliveframes, checks no server and no jar on the device after the viewer leaves the still screen, then releases a lease under a fresh viewer.
Written by an agent.
Activity